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