toolkit/components/places/nsNavHistoryResult.cpp

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

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

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

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

mercurial