summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/nsNavHistoryResult.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/nsNavHistoryResult.cpp')
-rw-r--r--toolkit/components/places/nsNavHistoryResult.cpp4813
1 files changed, 4813 insertions, 0 deletions
diff --git a/toolkit/components/places/nsNavHistoryResult.cpp b/toolkit/components/places/nsNavHistoryResult.cpp
new file mode 100644
index 000000000..7cd8c66cc
--- /dev/null
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -0,0 +1,4813 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include "nsNavHistory.h"
+#include "nsNavBookmarks.h"
+#include "nsFaviconService.h"
+#include "nsITaggingService.h"
+#include "nsAnnotationService.h"
+#include "Helpers.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDebug.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "nsQueryObject.h"
+
+#include "nsCycleCollectionParticipant.h"
+
+// Thanks, Windows.h :(
+#undef CompareString
+
+#define TO_ICONTAINER(_node) \
+ static_cast<nsINavHistoryContainerResultNode*>(_node)
+
+#define TO_CONTAINER(_node) \
+ static_cast<nsNavHistoryContainerResultNode*>(_node)
+
+#define NOTIFY_RESULT_OBSERVERS_RET(_result, _method, _ret) \
+ PR_BEGIN_MACRO \
+ NS_ENSURE_TRUE(_result, _ret); \
+ if (!_result->mSuppressNotifications) { \
+ ENUMERATE_WEAKARRAY(_result->mObservers, nsINavHistoryResultObserver, \
+ _method) \
+ } \
+ PR_END_MACRO
+
+#define NOTIFY_RESULT_OBSERVERS(_result, _method) \
+ NOTIFY_RESULT_OBSERVERS_RET(_result, _method, NS_ERROR_UNEXPECTED)
+
+// What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors,
+// but some of our classes (like nsNavHistoryResult) have an ambiguous base
+// class of nsISupports which prevents this from working (the default macro
+// converts it to nsISupports, then addrefs it, then returns it). Therefore, we
+// expand the macro here and change it so that it works. Yuck.
+#define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \
+ if (aIID.Equals(NS_GET_IID(_class))) { \
+ NS_ADDREF(this); \
+ *aInstancePtr = this; \
+ return NS_OK; \
+ } else
+
+// Number of changes to handle separately in a batch. If more changes are
+// requested the node will switch to full refresh mode.
+#define MAX_BATCH_CHANGES_BEFORE_REFRESH 5
+
+// Emulate string comparison (used for sorting) for PRTime and int.
+inline int32_t ComparePRTime(PRTime a, PRTime b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ return 0;
+}
+inline int32_t CompareIntegers(uint32_t a, uint32_t b)
+{
+ return a - b;
+}
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+NS_IMPL_CYCLE_COLLECTION(nsNavHistoryResultNode, mParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResultNode)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultNode)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
+
+nsNavHistoryResultNode::nsNavHistoryResultNode(
+ const nsACString& aURI, const nsACString& aTitle, uint32_t aAccessCount,
+ PRTime aTime, const nsACString& aIconURI) :
+ mParent(nullptr),
+ mURI(aURI),
+ mTitle(aTitle),
+ mAreTagsSorted(false),
+ mAccessCount(aAccessCount),
+ mTime(aTime),
+ mFaviconURI(aIconURI),
+ mBookmarkIndex(-1),
+ mItemId(-1),
+ mFolderId(-1),
+ mVisitId(-1),
+ mFromVisitId(-1),
+ mDateAdded(0),
+ mLastModified(0),
+ mIndentLevel(-1),
+ mFrecency(0),
+ mHidden(false),
+ mTransitionType(0)
+{
+ mTags.SetIsVoid(true);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetIcon(nsACString& aIcon)
+{
+ if (mFaviconURI.IsEmpty()) {
+ aIcon.Truncate();
+ return NS_OK;
+ }
+
+ nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
+ NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
+ faviconService->GetFaviconSpecForIconString(mFaviconURI, aIcon);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent)
+{
+ NS_IF_ADDREF(*aParent = mParent);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetParentResult(nsINavHistoryResult** aResult)
+{
+ *aResult = nullptr;
+ if (IsContainer())
+ NS_IF_ADDREF(*aResult = GetAsContainer()->mResult);
+ else if (mParent)
+ NS_IF_ADDREF(*aResult = mParent->mResult);
+
+ NS_ENSURE_STATE(*aResult);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetTags(nsAString& aTags) {
+ // Only URI-nodes may be associated with tags
+ if (!IsURI()) {
+ aTags.Truncate();
+ return NS_OK;
+ }
+
+ // Initially, the tags string is set to a void string (see constructor). We
+ // then build it the first time this method called is called (and by that,
+ // implicitly unset the void flag). Result observers may re-set the void flag
+ // in order to force rebuilding of the tags string.
+ if (!mTags.IsVoid()) {
+ // If mTags is assigned by a history query it is unsorted for performance
+ // reasons, it must be sorted by name on first read access.
+ if (!mAreTagsSorted) {
+ nsTArray<nsCString> tags;
+ ParseString(NS_ConvertUTF16toUTF8(mTags), ',', tags);
+ tags.Sort();
+ mTags.SetIsVoid(true);
+ for (nsTArray<nsCString>::index_type i = 0; i < tags.Length(); ++i) {
+ AppendUTF8toUTF16(tags[i], mTags);
+ if (i < tags.Length() - 1 )
+ mTags.AppendLiteral(", ");
+ }
+ mAreTagsSorted = true;
+ }
+ aTags.Assign(mTags);
+ return NS_OK;
+ }
+
+ // Fetch the tags
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
+ "/* do not warn (bug 487594) */ "
+ "SELECT GROUP_CONCAT(tag_title, ', ') "
+ "FROM ( "
+ "SELECT t.title AS tag_title "
+ "FROM moz_bookmarks b "
+ "JOIN moz_bookmarks t ON t.id = +b.parent "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "AND t.parent = :tags_folder "
+ "ORDER BY t.title COLLATE NOCASE ASC "
+ ") "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
+ history->GetTagsFolder());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasTags = false;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasTags)) && hasTags) {
+ rv = stmt->GetString(0, mTags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aTags.Assign(mTags);
+ mAreTagsSorted = true;
+ }
+
+ // If this node is a child of a history query, we need to make sure changes
+ // to tags are properly live-updated.
+ if (mParent && mParent->IsQuery() &&
+ mParent->mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
+ nsNavHistoryQueryResultNode* query = mParent->GetAsQuery();
+ nsNavHistoryResult* result = query->GetResult();
+ NS_ENSURE_STATE(result);
+ result->AddAllBookmarksObserver(query);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetPageGuid(nsACString& aPageGuid) {
+ aPageGuid = mPageGuid;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
+ aBookmarkGuid = mBookmarkGuid;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitId(int64_t* aVisitId) {
+ *aVisitId = mVisitId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetFromVisitId(int64_t* aFromVisitId) {
+ *aFromVisitId = mFromVisitId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitType(uint32_t* aVisitType) {
+ *aVisitType = mTransitionType;
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResultNode::OnRemoving()
+{
+ mParent = nullptr;
+}
+
+
+/**
+ * This will find the result for this node. We can ask the nearest container
+ * for this value (either ourselves or our parents should be a container,
+ * and all containers have result pointers).
+ *
+ * @note The result may be null, if the container is detached from the result
+ * who owns it.
+ */
+nsNavHistoryResult*
+nsNavHistoryResultNode::GetResult()
+{
+ nsNavHistoryResultNode* node = this;
+ do {
+ if (node->IsContainer()) {
+ nsNavHistoryContainerResultNode* container = TO_CONTAINER(node);
+ return container->mResult;
+ }
+ node = node->mParent;
+ } while (node);
+ MOZ_ASSERT(false, "No container node found in hierarchy!");
+ return nullptr;
+}
+
+
+/**
+ * Searches up the tree for the closest ancestor node that has an options
+ * structure. This will tell us the options that were used to generate this
+ * node.
+ *
+ * Be careful, this function walks up the tree, so it can not be used when
+ * result nodes are created because they have no parent. Only call this
+ * function after the tree has been built.
+ */
+nsNavHistoryQueryOptions*
+nsNavHistoryResultNode::GetGeneratingOptions()
+{
+ if (!mParent) {
+ // When we have no parent, it either means we haven't built the tree yet,
+ // in which case calling this function is a bug, or this node is the root
+ // of the tree. When we are the root of the tree, our own options are the
+ // generating options.
+ if (IsContainer())
+ return GetAsContainer()->mOptions;
+
+ NS_NOTREACHED("Can't find a generating node for this container, perhaps FillStats has not been called on this tree yet?");
+ return nullptr;
+ }
+
+ // Look up the tree. We want the options that were used to create this node,
+ // and since it has a parent, it's the options of an ancestor, not of the node
+ // itself. So start at the parent.
+ nsNavHistoryContainerResultNode* cur = mParent;
+ while (cur) {
+ if (cur->IsContainer())
+ return cur->GetAsContainer()->mOptions;
+ cur = cur->mParent;
+ }
+
+ // We should always find a container node as an ancestor.
+ NS_NOTREACHED("Can't find a generating node for this container, the tree seemes corrupted.");
+ return nullptr;
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode,
+ mResult,
+ mChildren)
+
+NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
+NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode)
+ NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
+NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
+
+nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryResultNode(aURI, aTitle, 0, 0, aIconURI),
+ mResult(nullptr),
+ mContainerType(aContainerType),
+ mExpanded(false),
+ mOptions(aOptions),
+ mAsyncCanceledState(NOT_CANCELED)
+{
+}
+
+nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ PRTime aTime,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryResultNode(aURI, aTitle, 0, aTime, aIconURI),
+ mResult(nullptr),
+ mContainerType(aContainerType),
+ mExpanded(false),
+ mOptions(aOptions),
+ mAsyncCanceledState(NOT_CANCELED)
+{
+}
+
+
+nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
+{
+ // Explicitly clean up array of children of this container. We must ensure
+ // all references are gone and all of their destructors are called.
+ mChildren.Clear();
+}
+
+
+/**
+ * Containers should notify their children that they are being removed when the
+ * container is being removed.
+ */
+void
+nsNavHistoryContainerResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+ mResult = nullptr;
+}
+
+
+bool
+nsNavHistoryContainerResultNode::AreChildrenVisible()
+{
+ nsNavHistoryResult* result = GetResult();
+ if (!result) {
+ NS_NOTREACHED("Invalid result");
+ return false;
+ }
+
+ if (!mExpanded)
+ return false;
+
+ // Now check if any ancestor is closed.
+ nsNavHistoryContainerResultNode* ancestor = mParent;
+ while (ancestor) {
+ if (!ancestor->mExpanded)
+ return false;
+
+ ancestor = ancestor->mParent;
+ }
+
+ return true;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetContainerOpen(bool *aContainerOpen)
+{
+ *aContainerOpen = mExpanded;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen)
+{
+ if (aContainerOpen) {
+ if (!mExpanded) {
+ nsNavHistoryQueryOptions* options = GetGeneratingOptions();
+ if (options && options->AsyncEnabled())
+ OpenContainerAsync();
+ else
+ OpenContainer();
+ }
+ }
+ else {
+ if (mExpanded)
+ CloseContainer();
+ else if (mAsyncPendingStmt)
+ CancelAsyncOpen(false);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * Notifies the result's observers of a change in the container's state. The
+ * notification includes both the old and new states: The old is aOldState, and
+ * the new is the container's current state.
+ *
+ * @param aOldState
+ * The state being transitioned out of.
+ */
+nsresult
+nsNavHistoryContainerResultNode::NotifyOnStateChange(uint16_t aOldState)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ nsresult rv;
+ uint16_t currState;
+ rv = GetState(&currState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify via the new ContainerStateChanged observer method.
+ NOTIFY_RESULT_OBSERVERS(result,
+ ContainerStateChanged(this, aOldState, currState));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetState(uint16_t* _state)
+{
+ NS_ENSURE_ARG_POINTER(_state);
+
+ *_state = mExpanded ? (uint16_t)STATE_OPENED
+ : mAsyncPendingStmt ? (uint16_t)STATE_LOADING
+ : (uint16_t)STATE_CLOSED;
+
+ return NS_OK;
+}
+
+
+/**
+ * This handles the generic container case. Other container types should
+ * override this to do their own handling.
+ */
+nsresult
+nsNavHistoryContainerResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must not be expanded to open it");
+ mExpanded = true;
+
+ nsresult rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * Unset aSuppressNotifications to notify observers on this change. That is
+ * the normal operation. This is set to false for the recursive calls since the
+ * root container that is being closed will handle recomputation of the visible
+ * elements for its entire subtree.
+ */
+nsresult
+nsNavHistoryContainerResultNode::CloseContainer(bool aSuppressNotifications)
+{
+ NS_ASSERTION((mExpanded && !mAsyncPendingStmt) ||
+ (!mExpanded && mAsyncPendingStmt),
+ "Container must be expanded or loading to close it");
+
+ nsresult rv;
+ uint16_t oldState;
+ rv = GetState(&oldState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mExpanded) {
+ // Recursively close all child containers.
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer() &&
+ mChildren[i]->GetAsContainer()->mExpanded)
+ mChildren[i]->GetAsContainer()->CloseContainer(true);
+ }
+
+ mExpanded = false;
+ }
+
+ // Be sure to set this to null before notifying observers. It signifies that
+ // the container is no longer loading (if it was in the first place).
+ mAsyncPendingStmt = nullptr;
+
+ if (!aSuppressNotifications) {
+ rv = NotifyOnStateChange(oldState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If this is the root container of a result, we can tell the result to stop
+ // observing changes, otherwise the result will stay in memory and updates
+ // itself till it is cycle collected.
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mRootNode == this) {
+ result->StopObserving();
+ // When reopening this node its result will be out of sync.
+ // We must clear our children to ensure we will call FillChildren
+ // again in such a case.
+ if (this->IsQuery())
+ this->GetAsQuery()->ClearChildren(true);
+ else if (this->IsFolder())
+ this->GetAsFolder()->ClearChildren(true);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * The async version of OpenContainer.
+ */
+nsresult
+nsNavHistoryContainerResultNode::OpenContainerAsync()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Cancels the pending asynchronous Storage execution triggered by
+ * FillChildrenAsync, if it exists. This method doesn't do much, because after
+ * cancelation Storage will call this node's HandleCompletion callback, where
+ * the real work is done.
+ *
+ * @param aRestart
+ * If true, async execution will be restarted by HandleCompletion.
+ */
+void
+nsNavHistoryContainerResultNode::CancelAsyncOpen(bool aRestart)
+{
+ NS_ASSERTION(mAsyncPendingStmt, "Async execution canceled but not pending");
+
+ mAsyncCanceledState = aRestart ? CANCELED_RESTART_NEEDED : CANCELED;
+
+ // Cancel will fail if the pending statement has already been canceled.
+ // That's OK since this method may be called multiple times, and multiple
+ // cancels don't harm anything.
+ (void)mAsyncPendingStmt->Cancel();
+}
+
+
+/**
+ * This builds up tree statistics from the bottom up. Call with a container
+ * and the indent level of that container. To init the full tree, call with
+ * the root container. The default indent level is -1, which is appropriate
+ * for the root level.
+ *
+ * CALL THIS AFTER FILLING ANY CONTAINER to update the parent and result node
+ * pointers, even if you don't care about visit counts and last visit dates.
+ */
+void
+nsNavHistoryContainerResultNode::FillStats()
+{
+ uint32_t accessCount = 0;
+ PRTime newTime = 0;
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsNavHistoryResultNode* node = mChildren[i];
+ node->mParent = this;
+ node->mIndentLevel = mIndentLevel + 1;
+ if (node->IsContainer()) {
+ nsNavHistoryContainerResultNode* container = node->GetAsContainer();
+ container->mResult = mResult;
+ container->FillStats();
+ }
+ accessCount += node->mAccessCount;
+ // this is how container nodes get sorted by date
+ // The container gets the most recent time of the child nodes.
+ if (node->mTime > newTime)
+ newTime = node->mTime;
+ }
+
+ if (mExpanded) {
+ mAccessCount = accessCount;
+ if (!IsQuery() || newTime > mTime)
+ mTime = newTime;
+ }
+}
+
+
+/**
+ * This is used when one container changes to do a minimal update of the tree
+ * structure. When something changes, you want to call FillStats if necessary
+ * and update this container completely. Then call this function which will
+ * walk up the tree and fill in the previous containers.
+ *
+ * Note that you have to tell us by how much our access count changed. Our
+ * access count should already be set to the new value; this is used tochange
+ * the parents without having to re-count all their children.
+ *
+ * This does NOT update the last visit date downward. Therefore, if you are
+ * deleting a node that has the most recent last visit date, the parents will
+ * not get their last visit dates downshifted accordingly. This is a rather
+ * unusual case: we don't often delete things, and we usually don't even show
+ * the last visit date for folders. Updating would be slower because we would
+ * have to recompute it from scratch.
+ */
+nsresult
+nsNavHistoryContainerResultNode::ReverseUpdateStats(int32_t aAccessCountChange)
+{
+ if (mParent) {
+ nsNavHistoryResult* result = GetResult();
+ bool shouldNotify = result && mParent->mParent &&
+ mParent->mParent->AreChildrenVisible();
+
+ mParent->mAccessCount += aAccessCountChange;
+ bool timeChanged = false;
+ if (mTime > mParent->mTime) {
+ timeChanged = true;
+ mParent->mTime = mTime;
+ }
+
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(TO_ICONTAINER(mParent),
+ mParent->mTime,
+ mParent->mAccessCount));
+ }
+
+ // check sorting, the stats may have caused this node to move if the
+ // sorting depended on something we are changing.
+ uint16_t sortMode = mParent->GetSortType();
+ bool sortingByVisitCount =
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING;
+ bool sortingByTime =
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING;
+
+ if ((sortingByVisitCount && aAccessCountChange != 0) ||
+ (sortingByTime && timeChanged)) {
+ int32_t ourIndex = mParent->FindChild(this);
+ NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
+ if (ourIndex >= 0)
+ EnsureItemPosition(static_cast<uint32_t>(ourIndex));
+ }
+
+ nsresult rv = mParent->ReverseUpdateStats(aAccessCountChange);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * This walks up the tree until we find a query result node or the root to get
+ * the sorting type.
+ */
+uint16_t
+nsNavHistoryContainerResultNode::GetSortType()
+{
+ if (mParent)
+ return mParent->GetSortType();
+ if (mResult)
+ return mResult->mSortingMode;
+
+ // This is a detached container, just use natural order.
+ return nsINavHistoryQueryOptions::SORT_BY_NONE;
+}
+
+
+nsresult nsNavHistoryContainerResultNode::Refresh() {
+ NS_WARNING("Refresh() is supported by queries or folders, not generic containers.");
+ return NS_OK;
+}
+
+void
+nsNavHistoryContainerResultNode::GetSortingAnnotation(nsACString& aAnnotation)
+{
+ if (mParent)
+ mParent->GetSortingAnnotation(aAnnotation);
+ else if (mResult)
+ aAnnotation.Assign(mResult->mSortingAnnotation);
+}
+
+/**
+ * @return the sorting comparator function for the give sort type, or null if
+ * there is no comparator.
+ */
+nsNavHistoryContainerResultNode::SortComparator
+nsNavHistoryContainerResultNode::GetSortingComparator(uint16_t aSortType)
+{
+ switch (aSortType)
+ {
+ case nsINavHistoryQueryOptions::SORT_BY_NONE:
+ return &SortComparison_Bookmark;
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
+ return &SortComparison_TitleLess;
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
+ return &SortComparison_TitleGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
+ return &SortComparison_DateLess;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
+ return &SortComparison_DateGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
+ return &SortComparison_URILess;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
+ return &SortComparison_URIGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
+ return &SortComparison_VisitCountLess;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
+ return &SortComparison_VisitCountGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_ASCENDING:
+ return &SortComparison_KeywordLess;
+ case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_DESCENDING:
+ return &SortComparison_KeywordGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING:
+ return &SortComparison_AnnotationLess;
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING:
+ return &SortComparison_AnnotationGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
+ return &SortComparison_DateAddedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
+ return &SortComparison_DateAddedGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
+ return &SortComparison_LastModifiedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
+ return &SortComparison_LastModifiedGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
+ return &SortComparison_TagsLess;
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
+ return &SortComparison_TagsGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
+ return &SortComparison_FrecencyLess;
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
+ return &SortComparison_FrecencyGreater;
+ default:
+ NS_NOTREACHED("Bad sorting type");
+ return nullptr;
+ }
+}
+
+
+/**
+ * This is used by Result::SetSortingMode and QueryResultNode::FillChildren to
+ * sort the child list.
+ *
+ * This does NOT update any visibility or tree information. The caller will
+ * have to completely rebuild the visible list after this.
+ */
+void
+nsNavHistoryContainerResultNode::RecursiveSort(
+ const char* aData, SortComparator aComparator)
+{
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ mChildren.Sort(aComparator, data);
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer())
+ mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
+ }
+}
+
+
+/**
+ * @return the index that the given item would fall on if it were to be
+ * inserted using the given sorting.
+ */
+uint32_t
+nsNavHistoryContainerResultNode::FindInsertionPoint(
+ nsNavHistoryResultNode* aNode, SortComparator aComparator,
+ const char* aData, bool* aItemExists)
+{
+ if (aItemExists)
+ (*aItemExists) = false;
+
+ if (mChildren.Count() == 0)
+ return 0;
+
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ // The common case is the beginning or the end because this is used to insert
+ // new items that are added to history, which is usually sorted by date.
+ int32_t res;
+ res = aComparator(aNode, mChildren[0], data);
+ if (res <= 0) {
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ return 0;
+ }
+ res = aComparator(aNode, mChildren[mChildren.Count() - 1], data);
+ if (res >= 0) {
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ return mChildren.Count();
+ }
+
+ uint32_t beginRange = 0; // inclusive
+ uint32_t endRange = mChildren.Count(); // exclusive
+ while (1) {
+ if (beginRange == endRange)
+ return endRange;
+ uint32_t center = beginRange + (endRange - beginRange) / 2;
+ int32_t res = aComparator(aNode, mChildren[center], data);
+ if (res <= 0) {
+ endRange = center; // left side
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ }
+ else {
+ beginRange = center + 1; // right site
+ }
+ }
+}
+
+
+/**
+ * This checks the child node at the given index to see if its sorting is
+ * correct. This is called when nodes are updated and we need to see whether
+ * we need to move it.
+ *
+ * @returns true if not and it should be resorted.
+*/
+bool
+nsNavHistoryContainerResultNode::DoesChildNeedResorting(uint32_t aIndex,
+ SortComparator aComparator, const char* aData)
+{
+ NS_ASSERTION(aIndex < uint32_t(mChildren.Count()),
+ "Input index out of range");
+ if (mChildren.Count() == 1)
+ return false;
+
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ if (aIndex > 0) {
+ // compare to previous item
+ if (aComparator(mChildren[aIndex - 1], mChildren[aIndex], data) > 0)
+ return true;
+ }
+ if (aIndex < uint32_t(mChildren.Count()) - 1) {
+ // compare to next item
+ if (aComparator(mChildren[aIndex], mChildren[aIndex + 1], data) > 0)
+ return true;
+ }
+ return false;
+}
+
+
+/* static */
+int32_t nsNavHistoryContainerResultNode::SortComparison_StringLess(
+ const nsAString& a, const nsAString& b) {
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, 0);
+ nsICollation* collation = history->GetCollation();
+ NS_ENSURE_TRUE(collation, 0);
+
+ int32_t res = 0;
+ collation->CompareString(nsICollation::kCollationCaseInSensitive, a, b, &res);
+ return res;
+}
+
+
+/**
+ * When there are bookmark indices, we should never have ties, so we don't
+ * need to worry about tiebreaking. When there are no bookmark indices,
+ * everything will be -1 and we don't worry about sorting.
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_Bookmark(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return a->mBookmarkIndex - b->mBookmarkIndex;
+}
+
+/**
+ * These are a little more complicated because they do a localization
+ * conversion. If this is too slow, we can compute the sort keys once in
+ * advance, sort that array, and then reorder the real array accordingly.
+ * This would save some key generations.
+ *
+ * The collation object must be allocated before sorting on title!
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_TitleLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ uint32_t aType;
+ a->GetType(&aType);
+
+ int32_t value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0) {
+ // resolve by URI
+ if (a->IsURI()) {
+ value = a->mURI.Compare(b->mURI.get());
+ }
+ if (value == 0) {
+ // resolve by date
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_TitleGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_TitleLess(a, b, closure);
+}
+
+/**
+ * Equal times will be very unusual, but it is important that there is some
+ * deterministic ordering of the results so they don't move around.
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mDateAdded, b->mDateAdded);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mLastModified, b->mLastModified);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(a, b, closure);
+}
+
+
+/**
+ * Certain types of parent nodes are treated specially because URIs are not
+ * valid (like days or hosts).
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_URILess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value;
+ if (a->IsURI() && b->IsURI()) {
+ // normal URI or visit
+ value = a->mURI.Compare(b->mURI.get());
+ } else {
+ // for everything else, use title (= host name)
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ }
+
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_URIGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_URILess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = 0;
+ if (a->mItemId != -1 || b->mItemId != -1) {
+ // compare the keywords
+ nsAutoString keywordA, keywordB;
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, 0);
+
+ nsresult rv;
+ if (a->mItemId != -1) {
+ rv = bookmarks->GetKeywordForBookmark(a->mItemId, keywordA);
+ NS_ENSURE_SUCCESS(rv, 0);
+ }
+ if (b->mItemId != -1) {
+ rv = bookmarks->GetKeywordForBookmark(b->mItemId, keywordB);
+ NS_ENSURE_SUCCESS(rv, 0);
+ }
+
+ value = SortComparison_StringLess(keywordA, keywordB);
+ }
+
+ // Fall back to title sorting.
+ if (value == 0)
+ value = SortComparison_TitleLess(a, b, closure);
+
+ return value;
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_KeywordLess(a, b, closure);
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ nsAutoCString annoName(static_cast<char*>(closure));
+ NS_ENSURE_TRUE(!annoName.IsEmpty(), 0);
+
+ bool a_itemAnno = false;
+ bool b_itemAnno = false;
+
+ // Not used for item annos
+ nsCOMPtr<nsIURI> a_uri, b_uri;
+ if (a->mItemId != -1) {
+ a_itemAnno = true;
+ } else {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(a->GetUri(spec))){
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(a_uri), spec));
+ }
+ NS_ENSURE_TRUE(a_uri, 0);
+ }
+
+ if (b->mItemId != -1) {
+ b_itemAnno = true;
+ } else {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(b->GetUri(spec))) {
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(b_uri), spec));
+ }
+ NS_ENSURE_TRUE(b_uri, 0);
+ }
+
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, 0);
+
+ bool a_hasAnno, b_hasAnno;
+ if (a_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(a->mItemId, annoName,
+ &a_hasAnno), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(a_uri, annoName,
+ &a_hasAnno), 0);
+ }
+ if (b_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(b->mItemId, annoName,
+ &b_hasAnno), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(b_uri, annoName,
+ &b_hasAnno), 0);
+ }
+
+ int32_t value = 0;
+ if (a_hasAnno || b_hasAnno) {
+ uint16_t annoType;
+ if (a_hasAnno) {
+ if (a_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(a->mItemId,
+ annoName,
+ &annoType), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(a_uri, annoName,
+ &annoType), 0);
+ }
+ }
+ if (b_hasAnno) {
+ uint16_t b_type;
+ if (b_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(b->mItemId,
+ annoName,
+ &b_type), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(b_uri, annoName,
+ &b_type), 0);
+ }
+ // We better make the API not support this state, really
+ // XXXmano: this is actually wrong for double<->int and int64_t<->int32_t
+ if (a_hasAnno && b_type != annoType)
+ return 0;
+ annoType = b_type;
+ }
+
+#define GET_ANNOTATIONS_VALUES(METHOD_ITEM, METHOD_PAGE, A_VAL, B_VAL) \
+ if (a_hasAnno) { \
+ if (a_itemAnno) { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(a->mItemId, annoName, \
+ A_VAL), 0); \
+ } else { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(a_uri, annoName, \
+ A_VAL), 0); \
+ } \
+ } \
+ if (b_hasAnno) { \
+ if (b_itemAnno) { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(b->mItemId, annoName, \
+ B_VAL), 0); \
+ } else { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(b_uri, annoName, \
+ B_VAL), 0); \
+ } \
+ }
+
+ if (annoType == nsIAnnotationService::TYPE_STRING) {
+ nsAutoString a_val, b_val;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationString,
+ GetPageAnnotationString, a_val, b_val);
+ value = SortComparison_StringLess(a_val, b_val);
+ }
+ else if (annoType == nsIAnnotationService::TYPE_INT32) {
+ int32_t a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationInt32,
+ GetPageAnnotationInt32, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ else if (annoType == nsIAnnotationService::TYPE_INT64) {
+ int64_t a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationInt64,
+ GetPageAnnotationInt64, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ else if (annoType == nsIAnnotationService::TYPE_DOUBLE) {
+ double a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationDouble,
+ GetPageAnnotationDouble, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ }
+
+ // Note we also fall back to the title-sorting route one of the items didn't
+ // have the annotation set or if both had it set but in a different storage
+ // type
+ if (value == 0)
+ return SortComparison_TitleLess(a, b, nullptr);
+
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_AnnotationLess(a, b, closure);
+}
+
+/**
+ * Fall back on dates for conflict resolution
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = CompareIntegers(a->mAccessCount, b->mAccessCount);
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_TagsLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = 0;
+ nsAutoString aTags, bTags;
+
+ nsresult rv = a->GetTags(aTags);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = b->GetTags(bTags);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ value = SortComparison_StringLess(aTags, bTags);
+
+ // fall back to title sorting
+ if (value == 0)
+ value = SortComparison_TitleLess(a, b, closure);
+
+ return value;
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_TagsGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_TagsLess(a, b, closure);
+}
+
+/**
+ * Fall back on date and bookmarked status, for conflict resolution.
+ */
+int32_t
+nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
+)
+{
+ int32_t value = CompareIntegers(a->mFrecency, b->mFrecency);
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0) {
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ }
+ return value;
+}
+int32_t
+nsNavHistoryContainerResultNode::SortComparison_FrecencyGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
+)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(a, b, closure);
+}
+
+/**
+ * Searches this folder for a node with the given URI. Returns null if not
+ * found.
+ *
+ * @note Does not addref the node!
+ */
+nsNavHistoryResultNode*
+nsNavHistoryContainerResultNode::FindChildURI(const nsACString& aSpec,
+ uint32_t* aNodeIndex)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsURI()) {
+ if (aSpec.Equals(mChildren[i]->mURI)) {
+ *aNodeIndex = i;
+ return mChildren[i];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * This does the work of adding a child to the container. The child can be
+ * either a container or or a single item that may even be collapsed with the
+ * adjacent ones.
+ */
+nsresult
+nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode,
+ int32_t aIndex)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ aNode->mParent = this;
+ aNode->mIndentLevel = mIndentLevel + 1;
+ if (aNode->IsContainer()) {
+ // need to update all the new item's children
+ nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
+ container->mResult = result;
+ container->FillStats();
+ }
+
+ if (!mChildren.InsertObjectAt(aNode, aIndex))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Update our stats and notify the result's observers.
+ mAccessCount += aNode->mAccessCount;
+ if (mTime < aNode->mTime)
+ mTime = aNode->mTime;
+ if (!mParent || mParent->AreChildrenVisible()) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(TO_ICONTAINER(this),
+ mTime,
+ mAccessCount));
+ }
+
+ nsresult rv = ReverseUpdateStats(aNode->mAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update tree if we are visible. Note that we could be here and not
+ // expanded, like when there is a bookmark folder being updated because its
+ // parent is visible.
+ if (AreChildrenVisible())
+ NOTIFY_RESULT_OBSERVERS(result, NodeInserted(this, aNode, aIndex));
+
+ return NS_OK;
+}
+
+
+/**
+ * This locates the proper place for insertion according to the current sort
+ * and calls InsertChildAt
+ */
+nsresult
+nsNavHistoryContainerResultNode::InsertSortedChild(
+ nsNavHistoryResultNode* aNode,
+ bool aIgnoreDuplicates)
+{
+
+ if (mChildren.Count() == 0)
+ return InsertChildAt(aNode, 0);
+
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ // When inserting a new node, it must have proper statistics because we use
+ // them to find the correct insertion point. The insert function will then
+ // recompute these statistics and fill in the proper parents and hierarchy
+ // level. Doing this twice shouldn't be a large performance penalty because
+ // when we are inserting new containers, they typically contain only one
+ // item (because we've browsed a new page).
+ if (aNode->IsContainer()) {
+ // need to update all the new item's children
+ nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
+ container->mResult = mResult;
+ container->FillStats();
+ }
+
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ bool itemExists;
+ uint32_t position = FindInsertionPoint(aNode, comparator,
+ sortingAnnotation.get(),
+ &itemExists);
+ if (aIgnoreDuplicates && itemExists)
+ return NS_OK;
+
+ return InsertChildAt(aNode, position);
+ }
+ return InsertChildAt(aNode, mChildren.Count());
+}
+
+/**
+ * This checks if the item at aIndex is located correctly given the sorting
+ * move. If it's not, the item is moved, and the result's observers are
+ * notified.
+ *
+ * @return true if the item position has been changed, false otherwise.
+ */
+bool
+nsNavHistoryContainerResultNode::EnsureItemPosition(uint32_t aIndex) {
+ NS_ASSERTION(aIndex < (uint32_t)mChildren.Count(), "Invalid index");
+ if (aIndex >= (uint32_t)mChildren.Count())
+ return false;
+
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (!comparator)
+ return false;
+
+ nsAutoCString sortAnno;
+ GetSortingAnnotation(sortAnno);
+ if (!DoesChildNeedResorting(aIndex, comparator, sortAnno.get()))
+ return false;
+
+ RefPtr<nsNavHistoryResultNode> node(mChildren[aIndex]);
+ mChildren.RemoveObjectAt(aIndex);
+
+ uint32_t newIndex = FindInsertionPoint(
+ node, comparator,sortAnno.get(), nullptr);
+ mChildren.InsertObjectAt(node.get(), newIndex);
+
+ if (AreChildrenVisible()) {
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS_RET(result,
+ NodeMoved(node, this, aIndex, this, newIndex),
+ false);
+ }
+
+ return true;
+}
+
+/**
+ * This does all the work of removing a child from this container, including
+ * updating the tree if necessary. Note that we do not need to be open for
+ * this to work.
+ */
+nsresult
+nsNavHistoryContainerResultNode::RemoveChildAt(int32_t aIndex)
+{
+ NS_ASSERTION(aIndex >= 0 && aIndex < mChildren.Count(), "Invalid index");
+
+ // Hold an owning reference to keep from expiring while we work with it.
+ RefPtr<nsNavHistoryResultNode> oldNode = mChildren[aIndex];
+
+ // Update stats.
+ // XXX This assertion does not reliably pass -- investigate!! (bug 1049797)
+ // MOZ_ASSERT(mAccessCount >= mChildren[aIndex]->mAccessCount,
+ // "Invalid access count while updating!");
+ uint32_t oldAccessCount = mAccessCount;
+ mAccessCount -= mChildren[aIndex]->mAccessCount;
+
+ // Remove it from our list and notify the result's observers.
+ mChildren.RemoveObjectAt(aIndex);
+ if (AreChildrenVisible()) {
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeRemoved(this, oldNode, aIndex));
+ }
+
+ nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ oldNode->OnRemoving();
+ return NS_OK;
+}
+
+
+/**
+ * Searches for matches for the given URI. If aOnlyOne is set, it will
+ * terminate as soon as it finds a single match. This would be used when there
+ * are URI results so there will only ever be one copy of any URI.
+ *
+ * When aOnlyOne is false, it will check all elements. This is for visit
+ * style results that may have multiple copies of any given URI.
+ */
+void
+nsNavHistoryContainerResultNode::RecursiveFindURIs(bool aOnlyOne,
+ nsNavHistoryContainerResultNode* aContainer, const nsCString& aSpec,
+ nsCOMArray<nsNavHistoryResultNode>* aMatches)
+{
+ for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
+ uint32_t type;
+ aContainer->mChildren[child]->GetType(&type);
+ if (nsNavHistoryResultNode::IsTypeURI(type)) {
+ // compare URIs
+ nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
+ if (uriNode->mURI.Equals(aSpec)) {
+ // found
+ aMatches->AppendObject(uriNode);
+ if (aOnlyOne)
+ return;
+ }
+ }
+ }
+}
+
+
+/**
+ * If aUpdateSort is true, we will also update the sorting of this item.
+ * Normally you want this to be true, but it can be false if the thing you are
+ * changing can not affect sorting (like favicons).
+ *
+ * You should NOT change any child lists as part of the callback function.
+ */
+bool
+nsNavHistoryContainerResultNode::UpdateURIs(bool aRecursive, bool aOnlyOne,
+ bool aUpdateSort, const nsCString& aSpec,
+ nsresult (*aCallback)(nsNavHistoryResultNode*, const void*, const nsNavHistoryResult*),
+ const void* aClosure)
+{
+ const nsNavHistoryResult* result = GetResult();
+ if (!result) {
+ MOZ_ASSERT(false, "Should have a result");
+ return false;
+ }
+
+ // this needs to be owning since sometimes we remove and re-insert nodes
+ // in their parents and we don't want them to go away.
+ nsCOMArray<nsNavHistoryResultNode> matches;
+
+ if (aRecursive) {
+ RecursiveFindURIs(aOnlyOne, this, aSpec, &matches);
+ } else if (aOnlyOne) {
+ uint32_t nodeIndex;
+ nsNavHistoryResultNode* node = FindChildURI(aSpec, &nodeIndex);
+ if (node)
+ matches.AppendObject(node);
+ } else {
+ MOZ_ASSERT(false,
+ "UpdateURIs does not handle nonrecursive updates of multiple items.");
+ // this case easy to add if you need it, just find all the matching URIs
+ // at this level. However, this isn't currently used. History uses
+ // recursive, Bookmarks uses one level and knows that the match is unique.
+ return false;
+ }
+
+ if (matches.Count() == 0)
+ return false;
+
+ // PERFORMANCE: This updates each container for each child in it that
+ // changes. In some cases, many elements have changed inside the same
+ // container. It would be better to compose a list of containers, and
+ // update each one only once for all the items that have changed in it.
+ for (int32_t i = 0; i < matches.Count(); ++i)
+ {
+ nsNavHistoryResultNode* node = matches[i];
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ if (!parent) {
+ MOZ_ASSERT(false, "All URI nodes being updated must have parents");
+ continue;
+ }
+
+ uint32_t oldAccessCount = node->mAccessCount;
+ PRTime oldTime = node->mTime;
+ aCallback(node, aClosure, result);
+
+ if (oldAccessCount != node->mAccessCount || oldTime != node->mTime) {
+ parent->mAccessCount += node->mAccessCount - oldAccessCount;
+ if (node->mTime > parent->mTime)
+ parent->mTime = node->mTime;
+ if (parent->AreChildrenVisible()) {
+ NOTIFY_RESULT_OBSERVERS_RET(result,
+ NodeHistoryDetailsChanged(
+ TO_ICONTAINER(parent),
+ parent->mTime,
+ parent->mAccessCount),
+ true);
+ }
+ DebugOnly<nsresult> rv = parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "should be able to ReverseUpdateStats");
+ }
+
+ if (aUpdateSort) {
+ int32_t childIndex = parent->FindChild(node);
+ MOZ_ASSERT(childIndex >= 0, "Could not find child we just got a reference to");
+ if (childIndex >= 0)
+ parent->EnsureItemPosition(childIndex);
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * This is used to update the titles in the tree. This is called from both
+ * query and bookmark folder containers to update the tree. Bookmark folders
+ * should be sure to set recursive to false, since child folders will have
+ * their own callbacks registered.
+ */
+static nsresult setTitleCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsACString* newTitle = static_cast<const nsACString*>(aClosure);
+ aNode->mTitle = *newTitle;
+
+ if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
+ NOTIFY_RESULT_OBSERVERS(aResult, NodeTitleChanged(aNode, *newTitle));
+
+ return NS_OK;
+}
+nsresult
+nsNavHistoryContainerResultNode::ChangeTitles(nsIURI* aURI,
+ const nsACString& aNewTitle,
+ bool aRecursive,
+ bool aOnlyOne)
+{
+ // uri string
+ nsAutoCString uriString;
+ nsresult rv = aURI->GetSpec(uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The recursive function will update the result's tree nodes, but only if we
+ // give it a non-null pointer. So if there isn't a tree, just pass nullptr
+ // so it doesn't bother trying to call the result.
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ uint16_t sortType = GetSortType();
+ bool updateSorting =
+ (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING);
+
+ UpdateURIs(aRecursive, aOnlyOne, updateSorting, uriString,
+ setTitleCallback,
+ static_cast<const void*>(&aNewTitle));
+
+ return NS_OK;
+}
+
+
+/**
+ * Complex containers (folders and queries) will override this. Here, we
+ * handle the case of simple containers (like host groups) where the children
+ * are always stored.
+ */
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetHasChildren(bool *aHasChildren)
+{
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+}
+
+
+/**
+ * @throws if this node is closed.
+ */
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChildCount(uint32_t* aChildCount)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+ *aChildCount = mChildren.Count();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChild(uint32_t aIndex,
+ nsINavHistoryResultNode** _retval)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+ if (aIndex >= uint32_t(mChildren.Count()))
+ return NS_ERROR_INVALID_ARG;
+ NS_ADDREF(*_retval = mChildren[aIndex]);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChildIndex(nsINavHistoryResultNode* aNode,
+ uint32_t* _retval)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ int32_t nodeIndex = FindChild(static_cast<nsNavHistoryResultNode*>(aNode));
+ if (nodeIndex == -1)
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = nodeIndex;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::FindNodeByDetails(const nsACString& aURIString,
+ PRTime aTime,
+ int64_t aItemId,
+ bool aRecursive,
+ nsINavHistoryResultNode** _retval) {
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = nullptr;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->mURI.Equals(aURIString) &&
+ mChildren[i]->mTime == aTime &&
+ mChildren[i]->mItemId == aItemId) {
+ *_retval = mChildren[i];
+ break;
+ }
+
+ if (aRecursive && mChildren[i]->IsContainer()) {
+ nsNavHistoryContainerResultNode* asContainer =
+ mChildren[i]->GetAsContainer();
+ if (asContainer->mExpanded) {
+ nsresult rv = asContainer->FindNodeByDetails(aURIString, aTime,
+ aItemId,
+ aRecursive,
+ _retval);
+
+ if (NS_SUCCEEDED(rv) && _retval)
+ break;
+ }
+ }
+ }
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+/**
+ * HOW QUERY UPDATING WORKS
+ *
+ * Queries are different than bookmark folders in that we can not always do
+ * dynamic updates (easily) and updates are more expensive. Therefore, we do
+ * NOT query if we are not open and want to see if we have any children (for
+ * drawing a twisty) and always assume we will.
+ *
+ * When the container is opened, we execute the query and register the
+ * listeners. Like bookmark folders, we stay registered even when closed, and
+ * clear ourselves as soon as a message comes in. This lets us respond quickly
+ * if the user closes and reopens the container.
+ *
+ * We try to handle the most common notifications for the most common query
+ * types dynamically, that is, figuring out what should happen in response to
+ * a message without doing a requery. For complex changes or complex queries,
+ * we give up and requery.
+ */
+NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
+ nsNavHistoryContainerResultNode,
+ nsINavHistoryQueryResultNode)
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ const nsACString& aQueryURI) :
+ nsNavHistoryContainerResultNode(aQueryURI, aTitle, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ nullptr),
+ mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
+ mHasSearchTerms(false),
+ mContentsValid(false),
+ mBatchChanges(0)
+{
+}
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ aOptions),
+ mQueries(aQueries),
+ mContentsValid(false),
+ mBatchChanges(0),
+ mTransitions(mQueries[0]->Transitions())
+{
+ NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "History service missing");
+ if (history) {
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ }
+
+ // Collect transitions shared by all queries.
+ for (int32_t i = 1; i < mQueries.Count(); ++i) {
+ const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
+ for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
+ uint32_t transition = mTransitions.SafeElementAt(j, 0);
+ if (transition && !queryTransitions.Contains(transition))
+ mTransitions.RemoveElement(transition);
+ }
+ }
+}
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ PRTime aTime,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ aOptions),
+ mQueries(aQueries),
+ mContentsValid(false),
+ mBatchChanges(0),
+ mTransitions(mQueries[0]->Transitions())
+{
+ NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "History service missing");
+ if (history) {
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ }
+
+ // Collect transitions shared by all queries.
+ for (int32_t i = 1; i < mQueries.Count(); ++i) {
+ const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
+ for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
+ uint32_t transition = mTransitions.SafeElementAt(j, 0);
+ if (transition && !queryTransitions.Contains(transition))
+ mTransitions.RemoveElement(transition);
+ }
+ }
+}
+
+nsNavHistoryQueryResultNode::~nsNavHistoryQueryResultNode() {
+ // Remove this node from result's observers. We don't need to be notified
+ // anymore.
+ if (mResult && mResult->mAllBookmarksObservers.Contains(this))
+ mResult->RemoveAllBookmarksObserver(this);
+ if (mResult && mResult->mHistoryObservers.Contains(this))
+ mResult->RemoveHistoryObserver(this);
+}
+
+/**
+ * Whoever made us may want non-expanding queries. However, we always expand
+ * when we are the root node, or else asking for non-expanding queries would be
+ * useless. A query node is not expandable if excludeItems is set or if
+ * expandQueries is unset.
+ */
+bool
+nsNavHistoryQueryResultNode::CanExpand()
+{
+ if (IsContainersQuery())
+ return true;
+
+ // If ExcludeItems is set on the root or on the node itself, don't expand.
+ if ((mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ Options()->ExcludeItems())
+ return false;
+
+ // Check the ancestor container.
+ nsNavHistoryQueryOptions* options = GetGeneratingOptions();
+ if (options) {
+ if (options->ExcludeItems())
+ return false;
+ if (options->ExpandQueries())
+ return true;
+ }
+
+ if (mResult && mResult->mRootNode == this)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Some query with a particular result type can contain other queries. They
+ * must be always expandable
+ */
+bool
+nsNavHistoryQueryResultNode::IsContainersQuery()
+{
+ uint16_t resultType = Options()->ResultType();
+ return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
+}
+
+
+/**
+ * Here we do not want to call ContainerResultNode::OnRemoving since our own
+ * ClearChildren will do the same thing and more (unregister the observers).
+ * The base ResultNode::OnRemoving will clear some regular node stats, so it
+ * is OK.
+ */
+void
+nsNavHistoryQueryResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ ClearChildren(true);
+ mResult = nullptr;
+}
+
+
+/**
+ * Marks the container as open, rebuilding results if they are invalid. We
+ * may still have valid results if the container was previously open and
+ * nothing happened since closing it.
+ *
+ * We do not handle CloseContainer specially. The default one just marks the
+ * container as closed, but doesn't actually mark the results as invalid.
+ * The results will be invalidated by the next history or bookmark
+ * notification that comes in. This means if you open and close the item
+ * without anything happening in between, it will be fast (this actually
+ * happens when results are used as menus).
+ */
+nsresult
+nsNavHistoryQueryResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must be closed to open it");
+ mExpanded = true;
+
+ nsresult rv;
+
+ if (!CanExpand())
+ return NS_OK;
+ if (!mContentsValid) {
+ rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * When we have valid results we can always give an exact answer. When we
+ * don't we just assume we'll have results, since actually doing the query
+ * might be hard. This is used to draw twisties on the tree, so precise results
+ * don't matter.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
+{
+ *aHasChildren = false;
+
+ if (!CanExpand()) {
+ return NS_OK;
+ }
+
+ uint16_t resultType = mOptions->ResultType();
+
+ // Tags are always populated, otherwise they are removed.
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+ *aHasChildren = true;
+ return NS_OK;
+ }
+
+ // For tag containers query we must check if we have any tag
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
+ nsCOMPtr<nsITaggingService> tagging =
+ do_GetService(NS_TAGGINGSERVICE_CONTRACTID);
+ if (tagging) {
+ bool hasTags;
+ *aHasChildren = NS_SUCCEEDED(tagging->GetHasTags(&hasTags)) && hasTags;
+ }
+ return NS_OK;
+ }
+
+ // For history containers query we must check if we have any history
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ return history->GetHasHistoryEntries(aHasChildren);
+ }
+
+ //XXX: For other containers queries we must:
+ // 1. If it's open, just check mChildren for containers
+ // 2. Else null the view (keep it in a var), open container, check mChildren
+ // for containers, close container, reset the view
+
+ if (mContentsValid) {
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+ }
+ *aHasChildren = true;
+ return NS_OK;
+}
+
+
+/**
+ * This doesn't just return mURI because in the case of queries that may
+ * be lazily constructed from the query objects.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetUri(nsACString& aURI)
+{
+ nsresult rv = VerifyQueriesSerialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+ aURI = mURI;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetFolderItemId(int64_t* aItemId)
+{
+ *aItemId = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetTargetFolderGuid(nsACString& aGuid) {
+ aGuid = EmptyCString();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetQueries(uint32_t* queryCount,
+ nsINavHistoryQuery*** queries)
+{
+ nsresult rv = VerifyQueriesParsed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(mQueries.Count() > 0, "Must have >= 1 query");
+
+ *queries = static_cast<nsINavHistoryQuery**>
+ (moz_xmalloc(mQueries.Count() * sizeof(nsINavHistoryQuery*)));
+ NS_ENSURE_TRUE(*queries, NS_ERROR_OUT_OF_MEMORY);
+
+ for (int32_t i = 0; i < mQueries.Count(); ++i)
+ NS_ADDREF((*queries)[i] = mQueries[i]);
+ *queryCount = mQueries.Count();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetQueryOptions(
+ nsINavHistoryQueryOptions** aQueryOptions)
+{
+ *aQueryOptions = Options();
+ NS_ADDREF(*aQueryOptions);
+ return NS_OK;
+}
+
+/**
+ * Safe options getter, ensures queries are parsed first.
+ */
+nsNavHistoryQueryOptions*
+nsNavHistoryQueryResultNode::Options()
+{
+ nsresult rv = VerifyQueriesParsed();
+ if (NS_FAILED(rv))
+ return nullptr;
+ NS_ASSERTION(mOptions, "Options invalid, cannot generate from URI");
+ return mOptions;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::VerifyQueriesParsed()
+{
+ if (mQueries.Count() > 0) {
+ NS_ASSERTION(mOptions, "If a result has queries, it also needs options");
+ return NS_OK;
+ }
+ NS_ASSERTION(!mURI.IsEmpty(),
+ "Query nodes must have either a URI or query/options");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = history->QueryStringToQueryArray(mURI, &mQueries,
+ getter_AddRefs(mOptions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::VerifyQueriesSerialized()
+{
+ if (!mURI.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_ASSERTION(mQueries.Count() > 0 && mOptions,
+ "Query nodes must have either a URI or query/options");
+
+ nsTArray<nsINavHistoryQuery*> flatQueries;
+ flatQueries.SetCapacity(mQueries.Count());
+ for (int32_t i = 0; i < mQueries.Count(); ++i)
+ flatQueries.AppendElement(static_cast<nsINavHistoryQuery*>
+ (mQueries.ObjectAt(i)));
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = history->QueriesToQueryString(flatQueries.Elements(),
+ flatQueries.Length(),
+ mOptions, mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!mURI.IsEmpty());
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::FillChildren()
+{
+ NS_ASSERTION(!mContentsValid,
+ "Don't call FillChildren when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0,
+ "We are trying to fill children when there already are some");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ // get the results from the history service
+ nsresult rv = VerifyQueriesParsed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = history->GetQueryResults(this, mQueries, mOptions, &mChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // it is important to call FillStats to fill in the parents on all
+ // nodes and the result node pointers on the containers
+ FillStats();
+
+ uint16_t sortType = GetSortType();
+
+ if (mResult && mResult->mNeedsToApplySortingMode) {
+ // We should repopulate container and then apply sortingMode. To avoid
+ // sorting 2 times we simply do that here.
+ mResult->SetSortingMode(mResult->mSortingMode);
+ }
+ else if (mOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
+ sortType != nsINavHistoryQueryOptions::SORT_BY_NONE) {
+ // The default SORT_BY_NONE sorts by the bookmark index (position),
+ // which we do not have for history queries.
+ // Once we've computed all tree stats, we can sort, because containers will
+ // then have proper visit counts and dates.
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ // Usually containers queries results comes already sorted from the
+ // database, but some locales could have special rules to sort by title.
+ // RecursiveSort won't apply these rules to containers in containers
+ // queries because when setting sortingMode on the result we want to sort
+ // contained items (bug 473157).
+ // Base container RecursiveSort will sort both our children and all
+ // descendants, and is used in this case because we have to do manual
+ // title sorting.
+ // Query RecursiveSort will instead only sort descendants if we are a
+ // constinaersQuery, e.g. a grouped query that will return other queries.
+ // For other type of queries it will act as the base one.
+ if (IsContainersQuery() &&
+ sortType == mOptions->SortingMode() &&
+ (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING))
+ nsNavHistoryContainerResultNode::RecursiveSort(sortingAnnotation.get(), comparator);
+ else
+ RecursiveSort(sortingAnnotation.get(), comparator);
+ }
+ }
+
+ // if we are limiting our results remove items from the end of the
+ // mChildren array after sorting. This is done for root node only.
+ // note, if count < max results, we won't do anything.
+ if (!mParent && mOptions->MaxResults()) {
+ while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
+ mChildren.RemoveObjectAt(mChildren.Count() - 1);
+ }
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
+ mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED) {
+ // Date containers that contain site containers have no reason to observe
+ // history, if the inside site container is expanded it will update,
+ // otherwise we are going to refresh the parent query.
+ if (!mParent || mParent->mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ // register with the result for history updates
+ result->AddHistoryObserver(this);
+ }
+ }
+
+ if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS ||
+ mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED ||
+ mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS ||
+ mHasSearchTerms) {
+ // register with the result for bookmark updates
+ result->AddAllBookmarksObserver(this);
+ }
+
+ mContentsValid = true;
+ return NS_OK;
+}
+
+
+/**
+ * Call with unregister = false when we are going to update the children (for
+ * example, when the container is open). This will clear the list and notify
+ * all the children that they are going away.
+ *
+ * When the results are becoming invalid and we are not going to refresh them,
+ * set unregister = true, which will unregister the listener from the
+ * result if any. We use unregister = false when we are refreshing the list
+ * immediately so want to stay a notifier.
+ */
+void
+nsNavHistoryQueryResultNode::ClearChildren(bool aUnregister)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+
+ if (aUnregister && mContentsValid) {
+ nsNavHistoryResult* result = GetResult();
+ if (result) {
+ result->RemoveHistoryObserver(this);
+ result->RemoveAllBookmarksObserver(this);
+ }
+ }
+ mContentsValid = false;
+}
+
+
+/**
+ * This is called to update the result when something has changed that we
+ * can not incrementally update.
+ */
+nsresult
+nsNavHistoryQueryResultNode::Refresh()
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress) {
+ result->requestRefresh(this);
+ return NS_OK;
+ }
+
+ // This is not a root node but it does not have a parent - this means that
+ // the node has already been cleared and it is now called, because it was
+ // left in a local copy of the observers array.
+ if (mIndentLevel > -1 && !mParent)
+ return NS_OK;
+
+ // Do not refresh if we are not expanded or if we are child of a query
+ // containing other queries. In this case calling Refresh for each child
+ // query could cause a major slowdown. We should not refresh nested
+ // queries, since we will already refresh the parent one.
+ if (!mExpanded ||
+ (mParent && mParent->IsQuery() &&
+ mParent->GetAsQuery()->IsContainersQuery())) {
+ // Don't update, just invalidate and unhook
+ ClearChildren(true);
+ return NS_OK; // no updates in tree state
+ }
+
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
+ ClearChildren(true);
+ else
+ ClearChildren(false);
+
+ // Ignore errors from FillChildren, since we will still want to refresh
+ // the tree (there just might not be anything in it on error).
+ (void)FillChildren();
+
+ NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
+ return NS_OK;
+}
+
+
+/**
+ * Here, we override GetSortType to return the current sorting for this
+ * query. GetSortType is used when dynamically inserting query results so we
+ * can see which comparator we should use to find the proper insertion point
+ * (it shouldn't be called from folder containers which maintain their own
+ * sorting).
+ *
+ * Normally, the container just forwards it up the chain. This is what we want
+ * for host groups, for example. For queries, we often want to use the query's
+ * sorting mode.
+ *
+ * However, we only use this query node's sorting when it is not the root.
+ * When it is the root, we use the result's sorting mode. This is because
+ * there are two cases:
+ * - You are looking at a bookmark hierarchy that contains an embedded
+ * result. We should always use the query's sort ordering since the result
+ * node's headers have nothing to do with us (and are disabled).
+ * - You are looking at a query in the tree. In this case, we want the
+ * result sorting to override ours (it should be initialized to the same
+ * sorting mode).
+ */
+uint16_t
+nsNavHistoryQueryResultNode::GetSortType()
+{
+ if (mParent)
+ return mOptions->SortingMode();
+ if (mResult)
+ return mResult->mSortingMode;
+
+ // This is a detached container, just use natural order.
+ return nsINavHistoryQueryOptions::SORT_BY_NONE;
+}
+
+
+void
+nsNavHistoryQueryResultNode::GetSortingAnnotation(nsACString& aAnnotation) {
+ if (mParent) {
+ // use our sorting, we are not the root
+ mOptions->GetSortingAnnotation(aAnnotation);
+ }
+ else if (mResult) {
+ aAnnotation.Assign(mResult->mSortingAnnotation);
+ }
+}
+
+void
+nsNavHistoryQueryResultNode::RecursiveSort(
+ const char* aData, SortComparator aComparator)
+{
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ if (!IsContainersQuery())
+ mChildren.Sort(aComparator, data);
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer())
+ mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
+ }
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnBeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnEndUpdateBatch()
+{
+ // If the query has no children it's possible it's not yet listening to
+ // bookmarks changes, in such a case it's safer to force a refresh to gather
+ // eventual new nodes matching query options.
+ if (mChildren.Count() == 0) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mBatchChanges = 0;
+ return NS_OK;
+}
+
+static nsresult setHistoryDetailsCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsNavHistoryResultNode* updatedNode =
+ static_cast<const nsNavHistoryResultNode*>(aClosure);
+
+ aNode->mAccessCount = updatedNode->mAccessCount;
+ aNode->mTime = updatedNode->mTime;
+ aNode->mFrecency = updatedNode->mFrecency;
+ aNode->mHidden = updatedNode->mHidden;
+
+ return NS_OK;
+}
+
+/**
+ * Here we need to update all copies of the URI we have with the new visit
+ * count, and potentially add a new entry in our query. This is the most
+ * common update operation and it is important that it be as efficient as
+ * possible.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnVisit(nsIURI* aURI, int64_t aVisitId,
+ PRTime aTime, int64_t aSessionId,
+ int64_t aReferringId,
+ uint32_t aTransitionType,
+ const nsACString& aGUID,
+ bool aHidden,
+ uint32_t* aAdded)
+{
+ if (aHidden && !mOptions->IncludeHidden())
+ return NS_OK;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ switch(mLiveUpdate) {
+ case QUERYUPDATE_HOST: {
+ // For these simple yet common cases we can check the host ourselves
+ // before doing the overhead of creating a new result node.
+ MOZ_ASSERT(mQueries.Count() == 1,
+ "Host updated queries can have only one object");
+ RefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
+
+ bool hasDomain;
+ query->GetHasDomain(&hasDomain);
+ if (!hasDomain)
+ return NS_OK;
+
+ nsAutoCString host;
+ if (NS_FAILED(aURI->GetAsciiHost(host)))
+ return NS_OK;
+
+ if (!query->Domain().Equals(host))
+ return NS_OK;
+
+ // Fall through to check the time, if the time is not present it will
+ // still match.
+ MOZ_FALLTHROUGH;
+ }
+
+ case QUERYUPDATE_TIME: {
+ // For these simple yet common cases we can check the time ourselves
+ // before doing the overhead of creating a new result node.
+ MOZ_ASSERT(mQueries.Count() == 1,
+ "Time updated queries can have only one object");
+ RefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
+
+ bool hasIt;
+ query->GetHasBeginTime(&hasIt);
+ if (hasIt) {
+ PRTime beginTime = history->NormalizeTime(query->BeginTimeReference(),
+ query->BeginTime());
+ if (aTime < beginTime)
+ return NS_OK; // before our time range
+ }
+ query->GetHasEndTime(&hasIt);
+ if (hasIt) {
+ PRTime endTime = history->NormalizeTime(query->EndTimeReference(),
+ query->EndTime());
+ if (aTime > endTime)
+ return NS_OK; // after our time range
+ }
+ // Now we know that our visit satisfies the time range, fall through to
+ // the QUERYUPDATE_SIMPLE case below.
+ MOZ_FALLTHROUGH;
+ }
+
+ case QUERYUPDATE_SIMPLE: {
+ // If all of the queries are filtered by some transitions, skip the
+ // update if aTransitionType doesn't match any of them.
+ if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
+ return NS_OK;
+
+ // The history service can tell us whether the new item should appear
+ // in the result. We first have to construct a node for it to check.
+ RefPtr<nsNavHistoryResultNode> addition;
+ nsresult rv = history->VisitIdToResultNode(aVisitId, mOptions,
+ getter_AddRefs(addition));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(addition);
+ addition->mTransitionType = aTransitionType;
+ if (!history->EvaluateQueryForNode(mQueries, mOptions, addition))
+ return NS_OK; // don't need to include in our query
+
+ if (mOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
+ // If this is a visit type query, just insert the new visit. We never
+ // update visits, only add or remove them.
+ rv = InsertSortedChild(addition);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ uint16_t sortType = GetSortType();
+ bool updateSorting =
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING;
+
+ if (!UpdateURIs(false, true, updateSorting, addition->mURI,
+ setHistoryDetailsCallback,
+ const_cast<void*>(static_cast<void*>(addition.get())))) {
+ // Couldn't find a node to update.
+ rv = InsertSortedChild(addition);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (aAdded)
+ ++(*aAdded);
+
+ break;
+ }
+
+ case QUERYUPDATE_COMPLEX:
+ case QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
+ // need to requery in complex cases
+ return Refresh();
+
+ default:
+ MOZ_ASSERT(false, "Invalid value for mLiveUpdate");
+ return Refresh();
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * Find every node that matches this URI and rename it. We try to do
+ * incremental updates here, even when we are closed, because changing titles
+ * is easier than requerying if we are invalid.
+ *
+ * This actually gets called a lot. Typically, we will get an AddURI message
+ * when the user visits the page, and then the title will be set asynchronously
+ * when the title element of the page is parsed.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
+ const nsAString& aPageTitle,
+ const nsACString& aGUID)
+{
+ if (!mExpanded) {
+ // When we are not expanded, we don't update, just invalidate and unhook.
+ // It would still be pretty easy to traverse the results and update the
+ // titles, but when a title changes, its unlikely that it will be the only
+ // thing. Therefore, we just give up.
+ ClearChildren(true);
+ return NS_OK; // no updates in tree state
+ }
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // compute what the new title should be
+ NS_ConvertUTF16toUTF8 newTitle(aPageTitle);
+
+ bool onlyOneEntry =
+ mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS;
+
+ // See if our queries have any search term matching.
+ if (mHasSearchTerms) {
+ // Find all matching URI nodes.
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+ if (matches.Count() == 0) {
+ // This could be a new node matching the query, thus we could need
+ // to add it to the result.
+ RefPtr<nsNavHistoryResultNode> node;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ rv = InsertSortedChild(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ // For each matched node we check if it passes the query filter, if not
+ // we remove the node from the result, otherwise we'll update the title
+ // later.
+ nsNavHistoryResultNode* node = matches[i];
+ // We must check the node with the new title.
+ node->mTitle = newTitle;
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ if (!history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ }
+ }
+ }
+
+ return ChangeTitles(aURI, newTitle, true, onlyOneEntry);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+
+/**
+ * Here, we can always live update by just deleting all occurrences of
+ * the given URI.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnDeleteURI(nsIURI* aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ if (IsContainersQuery()) {
+ // Incremental updates of query returning queries are pretty much
+ // complicated. In this case it's possible one of the child queries has
+ // no more children and it should be removed. Unfortunately there is no
+ // way to know that without executing the child query and counting results.
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ nsNavHistoryResultNode* node = matches[i];
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ if (parent->mChildren.Count() == 0 && parent->IsQuery() &&
+ parent->mIndentLevel > -1) {
+ // When query subcontainers (like hosts) get empty we should remove them
+ // as well. If the parent is not the root node, append it to our list
+ // and it will get evaluated later in the loop.
+ matches.AppendObject(parent);
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnClearHistory()
+{
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+
+static nsresult setFaviconCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsCString* newFavicon = static_cast<const nsCString*>(aClosure);
+ aNode->mFaviconURI = *newFavicon;
+
+ if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
+ NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnPageChanged(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString& aGUID)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (aChangedAttribute) {
+ case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
+ NS_ConvertUTF16toUTF8 newFavicon(aNewValue);
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
+ UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
+ &newFavicon);
+ break;
+ }
+ default:
+ NS_WARNING("Unknown page changed notification");
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnDeleteVisits(nsIURI* aURI,
+ PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason,
+ uint32_t aTransitionType)
+{
+ NS_PRECONDITION(mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY,
+ "Bookmarks queries should not get a OnDeleteVisits notification");
+ if (aVisitTime == 0) {
+ // All visits for this uri have been removed, but the uri won't be removed
+ // from the databse, most likely because it's a bookmark. For a history
+ // query this is equivalent to a onDeleteURI notification.
+ nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aTransitionType > 0) {
+ // All visits for aTransitionType have been removed, if the query is
+ // filtering on such transition type, this is equivalent to an onDeleteURI
+ // notification.
+ if (mTransitions.Length() > 0 && mTransitions.Contains(aTransitionType)) {
+ nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS
+ );
+
+ // Find matching URI nodes.
+ RefPtr<nsNavHistoryResultNode> node;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+
+ if (matches.Count() == 0 && mHasSearchTerms) {
+ // A new tag has been added, it's possible it matches our query.
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ rv = InsertSortedChild(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ nsNavHistoryResultNode* node = matches[i];
+ // Force a tags update before checking the node.
+ node->mTags.SetIsVoid(true);
+ nsAutoString tags;
+ rv = node->GetTags(tags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // It's possible now this node does not respect anymore the conditions.
+ // In such a case it should be removed.
+ if (mHasSearchTerms &&
+ !history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ }
+ else {
+ NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(node));
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * These are the bookmark observer functions for query nodes. They listen
+ * for bookmark events and refresh the results if we have any dependence on
+ * the bookmark system.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ // History observers should not get OnItemChanged
+ // but should get the corresponding history notifications instead.
+ // For bookmark queries, "all bookmark" observers should get OnItemChanged.
+ // For example, when a title of a bookmark changes, we want that to refresh.
+
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) {
+ switch (aItemType) {
+ case nsINavBookmarksService::TYPE_SEPARATOR:
+ // No separators in queries.
+ return NS_OK;
+ case nsINavBookmarksService::TYPE_FOLDER:
+ // Queries never result as "folders", but the tags-query results as
+ // special "tag" containers, which should follow their corresponding
+ // folders titles.
+ if (mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
+ return NS_OK;
+ MOZ_FALLTHROUGH;
+ default:
+ (void)Refresh();
+ }
+ }
+ else {
+ // Some node could observe both bookmarks and history. But a node observing
+ // only history should never get a bookmark notification.
+ NS_WARNING_ASSERTION(
+ mResult && (mResult->mIsAllBookmarksObserver ||
+ mResult->mIsBookmarkFolderObserver),
+ "history observers should not get OnItemChanged, but should get the "
+ "corresponding history notifications instead");
+
+ // Tags in history queries are a special case since tags are per uri and
+ // we filter tags based on searchterms.
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ aProperty.EqualsLiteral("tags")) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NotifyIfTagsChanged(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty,
+ aNewValue, aLastModified,
+ aItemType, aParentId, aGUID,
+ aParentGUID, aOldValue, aSource);
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ // for bookmark queries, "all bookmark" observer should get OnItemVisited
+ // but it is ignored.
+ if (mLiveUpdate != QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
+ NS_WARNING_ASSERTION(
+ mResult && (mResult->mIsAllBookmarksObserver ||
+ mResult->mIsBookmarkFolderObserver),
+ "history observers should not get OnItemVisited, but should get OnVisit "
+ "instead");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemMoved(int64_t aFolder,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ // 1. The query cannot be affected by the item's position
+ // 2. For the time being, we cannot optimize this not to update
+ // queries which are not restricted to some folders, due to way
+ // sub-queries are updated (see Refresh)
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS &&
+ aItemType != nsINavBookmarksService::TYPE_SEPARATOR &&
+ aOldParent != aNewParent) {
+ return Refresh();
+ }
+ return NS_OK;
+}
+
+/**
+ * HOW DYNAMIC FOLDER UPDATING WORKS
+ *
+ * When you create a result, it will automatically keep itself in sync with
+ * stuff that happens in the system. For folder nodes, this means changes to
+ * bookmarks.
+ *
+ * A folder will fill its children "when necessary." This means it is being
+ * opened or whether we need to see if it is empty for twisty drawing. It will
+ * then register its ID with the main result object that owns it. This result
+ * object will listen for all bookmark notifications and pass those
+ * notifications to folder nodes that have registered for that specific folder
+ * ID.
+ *
+ * When a bookmark folder is closed, it will not clear its children. Instead,
+ * it will keep them and also stay registered as a listener. This means that
+ * you can more quickly re-open the same folder without doing any work. This
+ * happens a lot for menus, and bookmarks don't change very often.
+ *
+ * When a message comes in and the folder is open, we will do the correct
+ * operations to keep ourselves in sync with the bookmark service. If the
+ * folder is closed, we just clear our list to mark it as invalid and
+ * unregister as a listener. This means we do not have to keep maintaining
+ * an up-to-date list for the entire bookmark menu structure in every place
+ * it is used.
+ */
+NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryFolderResultNode,
+ nsNavHistoryContainerResultNode,
+ nsINavHistoryQueryResultNode,
+ mozIStorageStatementCallback)
+
+nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
+ const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
+ int64_t aFolderId) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, EmptyCString(),
+ nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
+ aOptions),
+ mContentsValid(false),
+ mTargetFolderItemId(aFolderId),
+ mIsRegisteredFolderObserver(false)
+{
+ mItemId = aFolderId;
+}
+
+nsNavHistoryFolderResultNode::~nsNavHistoryFolderResultNode()
+{
+ if (mIsRegisteredFolderObserver && mResult)
+ mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
+}
+
+
+/**
+ * Here we do not want to call ContainerResultNode::OnRemoving since our own
+ * ClearChildren will do the same thing and more (unregister the observers).
+ * The base ResultNode::OnRemoving will clear some regular node stats, so it is
+ * OK.
+ */
+void
+nsNavHistoryFolderResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ ClearChildren(true);
+ mResult = nullptr;
+}
+
+
+nsresult
+nsNavHistoryFolderResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must be expanded to close it");
+ nsresult rv;
+
+ if (!mContentsValid) {
+ rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mExpanded = true;
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * The async version of OpenContainer.
+ */
+nsresult
+nsNavHistoryFolderResultNode::OpenContainerAsync()
+{
+ NS_ASSERTION(!mExpanded, "Container already expanded when opening it");
+
+ // If the children are valid, open the container synchronously. This will be
+ // the case when the container has already been opened and any other time
+ // FillChildren or FillChildrenAsync has previously been called.
+ if (mContentsValid)
+ return OpenContainer();
+
+ nsresult rv = FillChildrenAsync();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * @see nsNavHistoryQueryResultNode::HasChildren. The semantics here are a
+ * little different. Querying the contents of a bookmark folder is relatively
+ * fast and it is common to have empty folders. Therefore, we always want to
+ * return the correct result so that twisties are drawn properly.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetHasChildren(bool* aHasChildren)
+{
+ if (!mContentsValid) {
+ nsresult rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetFolderItemId(int64_t* aItemId)
+{
+ *aItemId = mTargetFolderItemId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetTargetFolderGuid(nsACString& aGuid) {
+ aGuid = mTargetFolderGuid;
+ return NS_OK;
+}
+
+/**
+ * Lazily computes the URI for this specific folder query with the current
+ * options.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetUri(nsACString& aURI)
+{
+ if (!mURI.IsEmpty()) {
+ aURI = mURI;
+ return NS_OK;
+ }
+
+ uint32_t queryCount;
+ nsINavHistoryQuery** queries;
+ nsresult rv = GetQueries(&queryCount, &queries);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = history->QueriesToQueryString(queries, queryCount, mOptions, aURI);
+ for (uint32_t queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
+ NS_RELEASE(queries[queryIndex]);
+ }
+ free(queries);
+ return rv;
+}
+
+
+/**
+ * @return the queries that give you this bookmarks folder
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetQueries(uint32_t* queryCount,
+ nsINavHistoryQuery*** queries)
+{
+ // get the query object
+ nsCOMPtr<nsINavHistoryQuery> query;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = history->GetNewQuery(getter_AddRefs(query));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // query just has the folder ID set and nothing else
+ rv = query->SetFolders(&mTargetFolderItemId, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make array of our 1 query
+ *queries = static_cast<nsINavHistoryQuery**>
+ (moz_xmalloc(sizeof(nsINavHistoryQuery*)));
+ if (!*queries)
+ return NS_ERROR_OUT_OF_MEMORY;
+ (*queries)[0] = query.forget().take();
+ *queryCount = 1;
+ return NS_OK;
+}
+
+
+/**
+ * Options for the query that gives you this bookmarks folder. This is just
+ * the options for the folder with the current folder ID set.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetQueryOptions(
+ nsINavHistoryQueryOptions** aQueryOptions)
+{
+ NS_ASSERTION(mOptions, "Options invalid");
+
+ *aQueryOptions = mOptions;
+ NS_ADDREF(*aQueryOptions);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryFolderResultNode::FillChildren()
+{
+ NS_ASSERTION(!mContentsValid,
+ "Don't call FillChildren when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0,
+ "We are trying to fill children when there already are some");
+
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+ // Actually get the folder children from the bookmark service.
+ nsresult rv = bookmarks->QueryFolderChildren(mTargetFolderItemId, mOptions, &mChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // PERFORMANCE: it may be better to also fill any child folders at this point
+ // so that we can draw tree twisties without doing a separate query later.
+ // If we don't end up drawing twisties a lot, it doesn't matter. If we do
+ // this, we should wrap everything in a transaction here on the bookmark
+ // service's connection.
+
+ return OnChildrenFilled();
+}
+
+
+/**
+ * Performs some tasks after all the children of the container have been added.
+ * The container's contents are not valid until this method has been called.
+ */
+nsresult
+nsNavHistoryFolderResultNode::OnChildrenFilled()
+{
+ // It is important to call FillStats to fill in the parents on all
+ // nodes and the result node pointers on the containers.
+ FillStats();
+
+ if (mResult && mResult->mNeedsToApplySortingMode) {
+ // We should repopulate container and then apply sortingMode. To avoid
+ // sorting 2 times we simply do that here.
+ mResult->SetSortingMode(mResult->mSortingMode);
+ }
+ else {
+ // Once we've computed all tree stats, we can sort, because containers will
+ // then have proper visit counts and dates.
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ RecursiveSort(sortingAnnotation.get(), comparator);
+ }
+ }
+
+ // If we are limiting our results remove items from the end of the
+ // mChildren array after sorting. This is done for root node only.
+ // Note, if count < max results, we won't do anything.
+ if (!mParent && mOptions->MaxResults()) {
+ while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
+ mChildren.RemoveObjectAt(mChildren.Count() - 1);
+ }
+
+ // Register with the result for updates.
+ EnsureRegisteredAsFolderObserver();
+
+ mContentsValid = true;
+ return NS_OK;
+}
+
+
+/**
+ * Registers the node with its result as a folder observer if it is not already
+ * registered.
+ */
+void
+nsNavHistoryFolderResultNode::EnsureRegisteredAsFolderObserver()
+{
+ if (!mIsRegisteredFolderObserver && mResult) {
+ mResult->AddBookmarkFolderObserver(this, mTargetFolderItemId);
+ mIsRegisteredFolderObserver = true;
+ }
+}
+
+
+/**
+ * The async version of FillChildren. This begins asynchronous execution by
+ * calling nsNavBookmarks::QueryFolderChildrenAsync. During execution, this
+ * node's async Storage callbacks, HandleResult and HandleCompletion, will be
+ * called.
+ */
+nsresult
+nsNavHistoryFolderResultNode::FillChildrenAsync()
+{
+ NS_ASSERTION(!mContentsValid, "FillChildrenAsync when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0, "FillChildrenAsync when children exist");
+
+ // ProcessFolderNodeChild, called in HandleResult, increments this for every
+ // result row it processes. Initialize it here as we begin async execution.
+ mAsyncBookmarkIndex = -1;
+
+ nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bmSvc, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv =
+ bmSvc->QueryFolderChildrenAsync(this, mTargetFolderItemId,
+ getter_AddRefs(mAsyncPendingStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Register with the result for updates. All updates during async execution
+ // will cause it to be restarted.
+ EnsureRegisteredAsFolderObserver();
+
+ return NS_OK;
+}
+
+
+/**
+ * A mozIStorageStatementCallback method. Called during the async execution
+ * begun by FillChildrenAsync.
+ *
+ * @param aResultSet
+ * The result set containing the data from the database.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::HandleResult(mozIStorageResultSet* aResultSet)
+{
+ NS_ENSURE_ARG_POINTER(aResultSet);
+
+ nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
+ if (!bmSvc) {
+ CancelAsyncOpen(false);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Consume all the currently available rows of the result set.
+ nsCOMPtr<mozIStorageRow> row;
+ while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
+ nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOptions, &mChildren,
+ mAsyncBookmarkIndex);
+ if (NS_FAILED(rv)) {
+ CancelAsyncOpen(false);
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * A mozIStorageStatementCallback method. Called during the async execution
+ * begun by FillChildrenAsync.
+ *
+ * @param aReason
+ * Indicates the final state of execution.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::HandleCompletion(uint16_t aReason)
+{
+ if (aReason == mozIStorageStatementCallback::REASON_FINISHED &&
+ mAsyncCanceledState == NOT_CANCELED) {
+ // Async execution successfully completed. The container is ready to open.
+
+ nsresult rv = OnChildrenFilled();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mExpanded = true;
+ mAsyncPendingStmt = nullptr;
+
+ // Notify observers only after mExpanded and mAsyncPendingStmt are set.
+ rv = NotifyOnStateChange(STATE_LOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ else if (mAsyncCanceledState == CANCELED_RESTART_NEEDED) {
+ // Async execution was canceled and needs to be restarted.
+ mAsyncCanceledState = NOT_CANCELED;
+ ClearChildren(false);
+ FillChildrenAsync();
+ }
+
+ else {
+ // Async execution failed or was canceled without restart. Remove all
+ // children and close the container, notifying observers.
+ mAsyncCanceledState = NOT_CANCELED;
+ ClearChildren(true);
+ CloseContainer();
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryFolderResultNode::ClearChildren(bool unregister)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+
+ bool needsUnregister = unregister && (mContentsValid || mAsyncPendingStmt);
+ if (needsUnregister && mResult && mIsRegisteredFolderObserver) {
+ mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
+ mIsRegisteredFolderObserver = false;
+ }
+ mContentsValid = false;
+}
+
+
+/**
+ * This is called to update the result when something has changed that we
+ * can not incrementally update.
+ */
+nsresult
+nsNavHistoryFolderResultNode::Refresh()
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress) {
+ result->requestRefresh(this);
+ return NS_OK;
+ }
+
+ ClearChildren(true);
+
+ if (!mExpanded) {
+ // When we are not expanded, we don't update, just invalidate and unhook.
+ return NS_OK;
+ }
+
+ // Ignore errors from FillChildren, since we will still want to refresh
+ // the tree (there just might not be anything in it on error). ClearChildren
+ // has unregistered us as an observer since FillChildren will try to
+ // re-register us.
+ (void)FillChildren();
+
+ NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
+ return NS_OK;
+}
+
+
+/**
+ * Implements the logic described above the constructor. This sees if we
+ * should do an incremental update and returns true if so. If not, it
+ * invalidates our children, unregisters us an observer, and returns false.
+ */
+bool
+nsNavHistoryFolderResultNode::StartIncrementalUpdate()
+{
+ // if any items are excluded, we can not do incremental updates since the
+ // indices from the bookmark service will not be valid
+
+ if (!mOptions->ExcludeItems() &&
+ !mOptions->ExcludeQueries() &&
+ !mOptions->ExcludeReadOnlyFolders()) {
+ // easy case: we are visible, always do incremental update
+ if (mExpanded || AreChildrenVisible())
+ return true;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_TRUE(result, false);
+
+ // When any observers are attached also do incremental updates if our
+ // parent is visible, so that twisties are drawn correctly.
+ if (mParent)
+ return result->mObservers.Length() > 0;
+ }
+
+ // otherwise, we don't do incremental updates, invalidate and unregister
+ (void)Refresh();
+ return false;
+}
+
+
+/**
+ * This function adds aDelta to all bookmark indices between the two endpoints,
+ * inclusive. It is used when items are added or removed from the bookmark
+ * folder.
+ */
+void
+nsNavHistoryFolderResultNode::ReindexRange(int32_t aStartIndex,
+ int32_t aEndIndex,
+ int32_t aDelta)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsNavHistoryResultNode* node = mChildren[i];
+ if (node->mBookmarkIndex >= aStartIndex &&
+ node->mBookmarkIndex <= aEndIndex)
+ node->mBookmarkIndex += aDelta;
+ }
+}
+
+
+/**
+ * Searches this folder for a node with the given id/target-folder-id.
+ *
+ * @return the node if found, null otherwise.
+ * @note Does not addref the node!
+ */
+nsNavHistoryResultNode*
+nsNavHistoryFolderResultNode::FindChildById(int64_t aItemId,
+ uint32_t* aNodeIndex)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->mItemId == aItemId ||
+ (mChildren[i]->IsFolder() &&
+ mChildren[i]->GetAsFolder()->mTargetFolderItemId == aItemId)) {
+ *aNodeIndex = i;
+ return mChildren[i];
+ }
+ }
+ return nullptr;
+}
+
+
+// Used by nsNavHistoryFolderResultNode's nsINavBookmarkObserver methods below.
+// If the container is notified of a bookmark event while asynchronous execution
+// is pending, this restarts it and returns.
+#define RESTART_AND_RETURN_IF_ASYNC_PENDING() \
+ if (mAsyncPendingStmt) { \
+ CancelAsyncOpen(true); \
+ return NS_OK; \
+ }
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnBeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnEndUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
+ int64_t aParentFolder,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ {
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (node)
+ return NS_OK;
+ }
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+
+ // here, try to do something reasonable if the bookmark service gives us
+ // a bogus index.
+ if (aIndex < 0) {
+ NS_NOTREACHED("Invalid index for item adding: <0");
+ aIndex = 0;
+ }
+ else if (aIndex > mChildren.Count()) {
+ if (!excludeItems) {
+ // Something wrong happened while updating indexes.
+ NS_NOTREACHED("Invalid index for item adding: greater than count");
+ }
+ aIndex = mChildren.Count();
+ }
+
+ nsresult rv;
+
+ // Check for query URIs, which are bookmarks, but treated as containers
+ // in results and views.
+ bool isQuery = false;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ NS_ASSERTION(aURI, "Got a null URI when we are a bookmark?!");
+ nsAutoCString itemURISpec;
+ rv = aURI->GetSpec(itemURISpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ isQuery = IsQueryURI(itemURISpec);
+ }
+
+ if (aItemType != nsINavBookmarksService::TYPE_FOLDER &&
+ !isQuery && excludeItems) {
+ // don't update items when we aren't displaying them, but we still need
+ // to adjust bookmark indices to account for the insertion
+ ReindexRange(aIndex, INT32_MAX, 1);
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // folder was completely refreshed for us
+
+ // adjust indices to account for insertion
+ ReindexRange(aIndex, INT32_MAX, 1);
+
+ RefPtr<nsNavHistoryResultNode> node;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->BookmarkIdToResultNode(aItemId, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aItemType == nsINavBookmarksService::TYPE_FOLDER) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ rv = bookmarks->ResultNodeForContainer(aItemId, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR) {
+ node = new nsNavHistorySeparatorResultNode();
+ NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
+ node->mItemId = aItemId;
+ node->mBookmarkGuid = aGUID;
+ node->mDateAdded = aDateAdded;
+ node->mLastModified = aDateAdded;
+ }
+
+ node->mBookmarkIndex = aIndex;
+
+ if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR ||
+ GetSortType() == nsINavHistoryQueryOptions::SORT_BY_NONE) {
+ // insert at natural bookmarks position
+ return InsertChildAt(node, aIndex);
+ }
+
+ // insert at sorted position
+ return InsertSortedChild(node);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemRemoved(int64_t aItemId,
+ int64_t aParentFolder,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ // Folder shortcuts should not be notified removal of the target folder.
+ MOZ_ASSERT_IF(mItemId != mTargetFolderItemId, aItemId != mTargetFolderItemId);
+ // Concrete folders should not be notified their own removal.
+ // Note aItemId may equal mItemId for recursive folder shortcuts.
+ MOZ_ASSERT_IF(mItemId == mTargetFolderItemId, aItemId != mItemId);
+
+ // In any case though, here we only care about the children removal.
+ if (mTargetFolderItemId == aItemId || mItemId == aItemId)
+ return NS_OK;
+
+ MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ // don't trust the index from the bookmark service, find it ourselves. The
+ // sorting could be different, or the bookmark services indices and ours might
+ // be out of sync somehow.
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (!node) {
+ return NS_OK;
+ }
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
+ // don't update items when we aren't displaying them, but we do need to
+ // adjust everybody's bookmark indices to account for the removal
+ ReindexRange(aIndex, INT32_MAX, -1);
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // we are completely refreshed
+
+ // shift all following indices down
+ ReindexRange(aIndex + 1, INT32_MAX, -1);
+
+ return RemoveChildAt(index);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ if (aItemId != mItemId)
+ return NS_OK;
+
+ mLastModified = aLastModified;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ bool shouldNotify = !mParent || mParent->AreChildrenVisible();
+
+ if (aIsAnnotationProperty) {
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeAnnotationChanged(this, aProperty));
+ }
+ else if (aProperty.EqualsLiteral("title")) {
+ // XXX: what should we do if the new title is void?
+ mTitle = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeTitleChanged(this, mTitle));
+ }
+ else if (aProperty.EqualsLiteral("uri")) {
+ // clear the tags string as well
+ mTags.SetIsVoid(true);
+ mURI = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, mURI));
+ }
+ else if (aProperty.EqualsLiteral("favicon")) {
+ mFaviconURI = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
+ }
+ else if (aProperty.EqualsLiteral("cleartime")) {
+ mTime = 0;
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(this, 0, mAccessCount));
+ }
+ }
+ else if (aProperty.EqualsLiteral("tags")) {
+ mTags.SetIsVoid(true);
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(this));
+ }
+ else if (aProperty.EqualsLiteral("dateAdded")) {
+ // aNewValue has the date as a string, but we can use aLastModified,
+ // because it's set to the same value when dateAdded is changed.
+ mDateAdded = aLastModified;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeDateAddedChanged(this, mDateAdded));
+ }
+ else if (aProperty.EqualsLiteral("lastModified")) {
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeLastModifiedChanged(this, aLastModified));
+ }
+ }
+ else if (aProperty.EqualsLiteral("keyword")) {
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeKeywordChanged(this, aNewValue));
+ }
+ else
+ NS_NOTREACHED("Unknown bookmark property changing.");
+
+ if (!mParent)
+ return NS_OK;
+
+ // DO NOT OPTIMIZE THIS TO CHECK aProperty
+ // The sorting methods fall back to each other so we need to re-sort the
+ // result even if it's not set to sort by the given property.
+ int32_t ourIndex = mParent->FindChild(this);
+ NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
+ if (ourIndex >= 0)
+ mParent->EnsureItemPosition(ourIndex);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty,
+ aNewValue, aLastModified,
+ aItemType, aParentId, aGUID,
+ aParentGUID, aOldValue, aSource);
+}
+
+/**
+ * Updates visit count and last visit time and refreshes.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if (excludeItems)
+ return NS_OK; // don't update items when we aren't displaying them
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ if (!StartIncrementalUpdate())
+ return NS_OK;
+
+ uint32_t nodeIndex;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &nodeIndex);
+ if (!node)
+ return NS_ERROR_FAILURE;
+
+ // Update node.
+ node->mTime = aTime;
+ ++node->mAccessCount;
+
+ // Update us.
+ int32_t oldAccessCount = mAccessCount;
+ ++mAccessCount;
+ if (aTime > mTime)
+ mTime = aTime;
+ nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update frecency for proper frecency ordering.
+ // TODO (bug 832617): we may avoid one query here, by providing the new
+ // frecency value in the notification.
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_OK);
+ RefPtr<nsNavHistoryResultNode> visitNode;
+ rv = history->VisitIdToResultNode(aVisitId, mOptions,
+ getter_AddRefs(visitNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(visitNode);
+ node->mFrecency = visitNode->mFrecency;
+
+ if (AreChildrenVisible()) {
+ // Sorting has not changed, just redraw the row if it's visible.
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(node, mTime, mAccessCount));
+ }
+
+ // Update sorting if necessary.
+ uint32_t sortType = GetSortType();
+ if (sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING) {
+ int32_t childIndex = FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Could not find child we just got a reference to");
+ if (childIndex >= 0) {
+ EnsureItemPosition(childIndex);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemMoved(int64_t aItemId,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ NS_ASSERTION(aOldParent == mTargetFolderItemId || aNewParent == mTargetFolderItemId,
+ "Got a bookmark message that doesn't belong to us");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (node && aNewParent == mTargetFolderItemId && index == static_cast<uint32_t>(aNewIndex))
+ return NS_OK;
+ if (!node && aOldParent == mTargetFolderItemId)
+ return NS_OK;
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if (node && excludeItems && (node->IsURI() || node->IsSeparator())) {
+ // Don't update items when we aren't displaying them.
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // entire container was refreshed for us
+
+ if (aOldParent == aNewParent) {
+ // getting moved within the same folder, we don't want to do a remove and
+ // an add because that will lose your tree state.
+
+ // adjust bookmark indices
+ ReindexRange(aOldIndex + 1, INT32_MAX, -1);
+ ReindexRange(aNewIndex, INT32_MAX, 1);
+
+ MOZ_ASSERT(node, "Can't find folder that is moving!");
+ if (!node) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(index < uint32_t(mChildren.Count()), "Invalid index!");
+ node->mBookmarkIndex = aNewIndex;
+
+ // adjust position
+ EnsureItemPosition(index);
+ return NS_OK;
+ } else {
+ // moving between two different folders, just do a remove and an add
+ nsCOMPtr<nsIURI> itemURI;
+ nsAutoCString itemTitle;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bookmarks->GetItemTitle(aItemId, itemTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aOldParent == mTargetFolderItemId) {
+ OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
+ aGUID, aOldParentGUID, aSource);
+ }
+ if (aNewParent == mTargetFolderItemId) {
+ OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
+ RoundedPRNow(), // This is a dummy dateAdded, not the real value.
+ aGUID, aNewParentGUID, aSource);
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Separator nodes do not hold any data.
+ */
+nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode()
+ : nsNavHistoryResultNode(EmptyCString(), EmptyCString(),
+ 0, 0, EmptyCString())
+{
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsNavHistoryResult)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNavHistoryResult)
+ tmp->StopObserving();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
+ for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ delete it.Data();
+ it.Remove();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAllBookmarksObservers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistoryObservers)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNavHistoryResult)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+ for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ nsNavHistoryResult::FolderObserverList*& list = it.Data();
+ for (uint32_t i = 0; i < list->Length(); ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mBookmarkFolderObservers value[i]");
+ nsNavHistoryResultNode* node = list->ElementAt(i);
+ cb.NoteXPCOMChild(node);
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAllBookmarksObservers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistoryObservers)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResult)
+ NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY(nsINavBookmarkObserver)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+nsNavHistoryResult::nsNavHistoryResult(nsNavHistoryContainerResultNode* aRoot)
+ : mRootNode(aRoot)
+ , mNeedsToApplySortingMode(false)
+ , mIsHistoryObserver(false)
+ , mIsBookmarkFolderObserver(false)
+ , mIsAllBookmarksObserver(false)
+ , mBookmarkFolderObservers(64)
+ , mBatchInProgress(false)
+ , mSuppressNotifications(false)
+{
+ mRootNode->mResult = this;
+}
+
+nsNavHistoryResult::~nsNavHistoryResult()
+{
+ // Delete all heap-allocated bookmark folder observer arrays.
+ for (auto it = mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ delete it.Data();
+ it.Remove();
+ }
+}
+
+void
+nsNavHistoryResult::StopObserving()
+{
+ if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (bookmarks) {
+ bookmarks->RemoveObserver(this);
+ mIsBookmarkFolderObserver = false;
+ mIsAllBookmarksObserver = false;
+ }
+ }
+ if (mIsHistoryObserver) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ if (history) {
+ history->RemoveObserver(this);
+ mIsHistoryObserver = false;
+ }
+ }
+}
+
+/**
+ * @note you must call AddRef before this, since we may do things like
+ * register ourselves.
+ */
+nsresult
+nsNavHistoryResult::Init(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions *aOptions)
+{
+ nsresult rv;
+ NS_ASSERTION(aOptions, "Must have valid options");
+ NS_ASSERTION(aQueries && aQueryCount > 0, "Must have >1 query in result");
+
+ // Fill saved source queries with copies of the original (the caller might
+ // change their original objects, and we always want to reflect the source
+ // parameters).
+ for (uint32_t i = 0; i < aQueryCount; ++i) {
+ nsCOMPtr<nsINavHistoryQuery> queryClone;
+ rv = aQueries[i]->Clone(getter_AddRefs(queryClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mQueries.AppendObject(queryClone))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = aOptions->Clone(getter_AddRefs(mOptions));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSortingMode = aOptions->SortingMode();
+ rv = aOptions->GetSortingAnnotation(mSortingAnnotation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(mRootNode->mIndentLevel == -1,
+ "Root node's indent level initialized wrong");
+ mRootNode->FillStats();
+
+ return NS_OK;
+}
+
+
+/**
+ * Constructs a new history result object.
+ */
+nsresult // static
+nsNavHistoryResult::NewHistoryResult(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryContainerResultNode* aRoot,
+ bool aBatchInProgress,
+ nsNavHistoryResult** result)
+{
+ *result = new nsNavHistoryResult(aRoot);
+ if (!*result)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result); // must happen before Init
+ // Correctly set mBatchInProgress for the result based on the root node value.
+ (*result)->mBatchInProgress = aBatchInProgress;
+ nsresult rv = (*result)->Init(aQueries, aQueryCount, aOptions);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*result);
+ *result = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ if (!mIsHistoryObserver) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "Can't create history service");
+ history->AddObserver(this, true);
+ mIsHistoryObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ if (mHistoryObservers.IndexOf(aNode) == mHistoryObservers.NoIndex) {
+ mHistoryObservers.AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ if (!mIsAllBookmarksObserver && !mIsBookmarkFolderObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (!bookmarks) {
+ NS_NOTREACHED("Can't create bookmark service");
+ return;
+ }
+ bookmarks->AddObserver(this, true);
+ mIsAllBookmarksObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ if (mAllBookmarksObservers.IndexOf(aNode) == mAllBookmarksObservers.NoIndex) {
+ mAllBookmarksObservers.AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolder)
+{
+ if (!mIsBookmarkFolderObserver && !mIsAllBookmarksObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (!bookmarks) {
+ NS_NOTREACHED("Can't create bookmark service");
+ return;
+ }
+ bookmarks->AddObserver(this, true);
+ mIsBookmarkFolderObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ FolderObserverList* list = BookmarkFolderObserversForId(aFolder, true);
+ if (list->IndexOf(aNode) == list->NoIndex) {
+ list->AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ mHistoryObservers.RemoveElement(aNode);
+}
+
+
+void
+nsNavHistoryResult::RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ mAllBookmarksObservers.RemoveElement(aNode);
+}
+
+
+void
+nsNavHistoryResult::RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolder)
+{
+ FolderObserverList* list = BookmarkFolderObserversForId(aFolder, false);
+ if (!list)
+ return; // we don't even have an entry for that folder
+ list->RemoveElement(aNode);
+}
+
+
+nsNavHistoryResult::FolderObserverList*
+nsNavHistoryResult::BookmarkFolderObserversForId(int64_t aFolderId, bool aCreate)
+{
+ FolderObserverList* list;
+ if (mBookmarkFolderObservers.Get(aFolderId, &list))
+ return list;
+ if (!aCreate)
+ return nullptr;
+
+ // need to create a new list
+ list = new FolderObserverList;
+ mBookmarkFolderObservers.Put(aFolderId, list);
+ return list;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSortingMode(uint16_t* aSortingMode)
+{
+ *aSortingMode = mSortingMode;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSortingMode(uint16_t aSortingMode)
+{
+ NS_ENSURE_STATE(mRootNode);
+
+ if (aSortingMode > nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING)
+ return NS_ERROR_INVALID_ARG;
+
+ // Keep everything in sync.
+ NS_ASSERTION(mOptions, "Options should always be present for a root query");
+
+ mSortingMode = aSortingMode;
+
+ if (!mRootNode->mExpanded) {
+ // Need to do this later when node will be expanded.
+ mNeedsToApplySortingMode = true;
+ return NS_OK;
+ }
+
+ // Actually do sorting.
+ nsNavHistoryContainerResultNode::SortComparator comparator =
+ nsNavHistoryContainerResultNode::GetSortingComparator(aSortingMode);
+ if (comparator) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ mRootNode->RecursiveSort(mSortingAnnotation.get(), comparator);
+ }
+
+ NOTIFY_RESULT_OBSERVERS(this, SortingChanged(aSortingMode));
+ NOTIFY_RESULT_OBSERVERS(this, InvalidateContainer(mRootNode));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSortingAnnotation(nsACString& _result) {
+ _result.Assign(mSortingAnnotation);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSortingAnnotation(const nsACString& aSortingAnnotation) {
+ mSortingAnnotation.Assign(aSortingAnnotation);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::AddObserver(nsINavHistoryResultObserver* aObserver,
+ bool aOwnsWeak)
+{
+ NS_ENSURE_ARG(aObserver);
+ nsresult rv = mObservers.AppendWeakElement(aObserver, aOwnsWeak);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aObserver->SetResult(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we are batching, notify a fake batch start to the observers.
+ // Not doing so would then notify a not coupled batch end.
+ if (mBatchInProgress) {
+ NOTIFY_RESULT_OBSERVERS(this, Batching(true));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::RemoveObserver(nsINavHistoryResultObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+ return mObservers.RemoveWeakElement(aObserver);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSuppressNotifications(bool* _retval)
+{
+ *_retval = mSuppressNotifications;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSuppressNotifications(bool aSuppressNotifications)
+{
+ mSuppressNotifications = aSuppressNotifications;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetRoot(nsINavHistoryContainerResultNode** aRoot)
+{
+ if (!mRootNode) {
+ NS_NOTREACHED("Root is null");
+ *aRoot = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsNavHistoryContainerResultNode> node(mRootNode);
+ node.forget(aRoot);
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResult::requestRefresh(nsNavHistoryContainerResultNode* aContainer)
+{
+ // Don't add twice the same container.
+ if (mRefreshParticipants.IndexOf(aContainer) == mRefreshParticipants.NoIndex)
+ mRefreshParticipants.AppendElement(aContainer);
+}
+
+// nsINavBookmarkObserver implementation
+
+// Here, it is important that we create a COPY of the observer array. Some
+// observers will requery themselves, which may cause the observer array to
+// be modified or added to.
+#define ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(_folderId, _functionCall) \
+ PR_BEGIN_MACRO \
+ FolderObserverList* _fol = BookmarkFolderObserversForId(_folderId, false); \
+ if (_fol) { \
+ FolderObserverList _listCopy(*_fol); \
+ for (uint32_t _fol_i = 0; _fol_i < _listCopy.Length(); ++_fol_i) { \
+ if (_listCopy[_fol_i]) \
+ _listCopy[_fol_i]->_functionCall; \
+ } \
+ } \
+ PR_END_MACRO
+#define ENUMERATE_LIST_OBSERVERS(_listType, _functionCall, _observersList, _conditionCall) \
+ PR_BEGIN_MACRO \
+ _listType _listCopy(_observersList); \
+ for (uint32_t _obs_i = 0; _obs_i < _listCopy.Length(); ++_obs_i) { \
+ if (_listCopy[_obs_i] && _listCopy[_obs_i]->_conditionCall) \
+ _listCopy[_obs_i]->_functionCall; \
+ } \
+ PR_END_MACRO
+#define ENUMERATE_QUERY_OBSERVERS(_functionCall, _observersList, _conditionCall) \
+ ENUMERATE_LIST_OBSERVERS(QueryObserverList, _functionCall, _observersList, _conditionCall)
+#define ENUMERATE_ALL_BOOKMARKS_OBSERVERS(_functionCall) \
+ ENUMERATE_QUERY_OBSERVERS(_functionCall, mAllBookmarksObservers, IsQuery())
+#define ENUMERATE_HISTORY_OBSERVERS(_functionCall) \
+ ENUMERATE_QUERY_OBSERVERS(_functionCall, mHistoryObservers, IsQuery())
+
+#define NOTIFY_REFRESH_PARTICIPANTS() \
+ PR_BEGIN_MACRO \
+ ENUMERATE_LIST_OBSERVERS(ContainerObserverList, Refresh(), mRefreshParticipants, IsContainer()); \
+ mRefreshParticipants.Clear(); \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnBeginUpdateBatch()
+{
+ // Since we could be observing both history and bookmarks, it's possible both
+ // notify the batch. We can safely ignore nested calls.
+ if (!mBatchInProgress) {
+ mBatchInProgress = true;
+ ENUMERATE_HISTORY_OBSERVERS(OnBeginUpdateBatch());
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnBeginUpdateBatch());
+
+ NOTIFY_RESULT_OBSERVERS(this, Batching(true));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnEndUpdateBatch()
+{
+ // Since we could be observing both history and bookmarks, it's possible both
+ // notify the batch. We can safely ignore nested calls.
+ // Notice it's possible we are notified OnEndUpdateBatch more times than
+ // onBeginUpdateBatch, since the result could be created in the middle of
+ // nested batches.
+ if (mBatchInProgress) {
+ ENUMERATE_HISTORY_OBSERVERS(OnEndUpdateBatch());
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnEndUpdateBatch());
+
+ // Setting mBatchInProgress before notifying the end of the batch to
+ // observers would make evantual calls to Refresh() directly handled rather
+ // than enqueued. Thus set it just before handling refreshes.
+ mBatchInProgress = false;
+ NOTIFY_REFRESH_PARTICIPANTS();
+ NOTIFY_RESULT_OBSERVERS(this, Batching(false));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
+ aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ ENUMERATE_HISTORY_OBSERVERS(
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
+ aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ ENUMERATE_HISTORY_OBSERVERS(
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemChanged(int64_t aItemId,
+ const nsACString &aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString &aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
+ aLastModified, aItemType, aParentId, aGUID, aParentGUID,
+ aOldValue, aSource));
+
+ // Note: folder-nodes set their own bookmark observer only once they're
+ // opened, meaning we cannot optimize this code path for changes done to
+ // folder-nodes.
+
+ FolderObserverList* list = BookmarkFolderObserversForId(aParentId, false);
+ if (!list)
+ return NS_OK;
+
+ for (uint32_t i = 0; i < list->Length(); ++i) {
+ RefPtr<nsNavHistoryFolderResultNode> folder = list->ElementAt(i);
+ if (folder) {
+ uint32_t nodeIndex;
+ RefPtr<nsNavHistoryResultNode> node =
+ folder->FindChildById(aItemId, &nodeIndex);
+ // if ExcludeItems is true we don't update non visible items
+ bool excludeItems = (mRootNode->mOptions->ExcludeItems()) ||
+ folder->mOptions->ExcludeItems();
+ if (node &&
+ (!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
+ folder->StartIncrementalUpdate()) {
+ node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
+ aNewValue, aLastModified, aItemType, aParentId,
+ aGUID, aParentGUID, aOldValue, aSource);
+ }
+ }
+ }
+
+ // Note: we do NOT call history observers in this case. This notification is
+ // the same as other history notification, except that here we know the item
+ // is a bookmark. History observers will handle the history notification
+ // instead.
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aVisitTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
+ aParentId, aGUID, aParentGUID));
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
+ aParentId, aGUID, aParentGUID));
+ // Note: we do NOT call history observers in this case. This notification is
+ // the same as OnVisit, except that here we know the item is a bookmark.
+ // History observers will handle the history notification instead.
+ return NS_OK;
+}
+
+
+/**
+ * Need to notify both the source and the destination folders (if they are
+ * different).
+ */
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemMoved(int64_t aItemId,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aOldParent,
+ OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
+ aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource));
+ if (aNewParent != aOldParent) {
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aNewParent,
+ OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
+ aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource));
+ }
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex,
+ aItemType, aGUID,
+ aOldParentGUID,
+ aNewParentGUID, aSource));
+ ENUMERATE_HISTORY_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex, aItemType,
+ aGUID, aOldParentGUID,
+ aNewParentGUID, aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
+ int64_t aSessionId, int64_t aReferringId,
+ uint32_t aTransitionType, const nsACString& aGUID,
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Embed visits are never shown in our views.
+ if (aTransitionType == nsINavHistoryService::TRANSITION_EMBED) {
+ return NS_OK;
+ }
+
+ uint32_t added = 0;
+
+ ENUMERATE_HISTORY_OBSERVERS(OnVisit(aURI, aVisitId, aTime, aSessionId,
+ aReferringId, aTransitionType, aGUID,
+ aHidden, &added));
+
+ if (!mRootNode->mExpanded)
+ return NS_OK;
+
+ // If this visit is accepted by an overlapped container, and not all
+ // overlapped containers are visible, we should still call Refresh if the
+ // visit falls into any of them.
+ bool todayIsMissing = false;
+ uint32_t resultType = mRootNode->mOptions->ResultType();
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ uint32_t childCount;
+ nsresult rv = mRootNode->GetChildCount(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (childCount) {
+ nsCOMPtr<nsINavHistoryResultNode> firstChild;
+ rv = mRootNode->GetChild(0, getter_AddRefs(firstChild));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString title;
+ rv = firstChild->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_OK);
+ nsAutoCString todayLabel;
+ history->GetStringFromName(
+ u"finduri-AgeInDays-is-0", todayLabel);
+ todayIsMissing = !todayLabel.Equals(title);
+ }
+ }
+
+ if (!added || todayIsMissing) {
+ // None of registered query observers has accepted our URI. This means,
+ // that a matching query either was not expanded or it does not exist.
+ uint32_t resultType = mRootNode->mOptions->ResultType();
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ // If the visit falls into the Today bucket and the bucket exists, it was
+ // just not expanded, thus there's no reason to update.
+ int64_t beginOfToday =
+ nsNavHistory::NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
+ if (todayIsMissing || aTime < beginOfToday) {
+ (void)mRootNode->GetAsQuery()->Refresh();
+ }
+ return NS_OK;
+ }
+
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
+ (void)mRootNode->GetAsQuery()->Refresh();
+ return NS_OK;
+ }
+
+ // We are result of a folder node, then we should run through history
+ // observers that are containers queries and refresh them.
+ // We use a copy of the observers array since requerying could potentially
+ // cause changes to the array.
+ ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
+ const nsAString& aPageTitle,
+ const nsACString& aGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnTitleChanged(aURI, aPageTitle, aGUID));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnDeleteURI(aURI, aGUID, aReason));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnClearHistory()
+{
+ ENUMERATE_HISTORY_OBSERVERS(OnClearHistory());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnPageChanged(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aValue,
+ const nsACString& aGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnPageChanged(aURI, aChangedAttribute, aValue, aGUID));
+ return NS_OK;
+}
+
+
+/**
+ * Don't do anything when visits expire.
+ */
+NS_IMETHODIMP
+nsNavHistoryResult::OnDeleteVisits(nsIURI* aURI,
+ PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason,
+ uint32_t aTransitionType)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnDeleteVisits(aURI, aVisitTime, aGUID, aReason,
+ aTransitionType));
+ return NS_OK;
+}