--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -235,43 +235,36 @@ var PlacesOrganizer = {
* deleting its text, this will be false.
*/
_cachedLeftPaneSelectedURI: null,
onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) {
// Don't change the right-hand pane contents when there's no selection.
if (!this._places.hasSelection)
return;
- var node = this._places.selectedNode;
- var queries = PlacesUtils.asQuery(node).getQueries();
-
- // Items are only excluded on the left pane.
- var options = node.queryOptions.clone();
- options.excludeItems = false;
- var placeURI = PlacesUtils.history.queriesToQueryString(queries,
- queries.length,
- options);
+ let node = this._places.selectedNode;
+ let placeURI = node.uri;
// If either the place of the content tree in the right pane has changed or
// the user cleared the search box, update the place, hide the search UI,
// and update the back/forward buttons by setting location.
if (ContentArea.currentPlace != placeURI || !resetSearchBox) {
ContentArea.currentPlace = placeURI;
- this.location = node.uri;
+ this.location = placeURI;
}
// When we invalidate a container we use suppressSelectionEvent, when it is
// unset a select event is fired, in many cases the selection did not really
// change, so we should check for it, and return early in such a case. Note
// that we cannot return any earlier than this point, because when
// !resetSearchBox, we need to update location and hide the UI as above,
// even though the selection has not changed.
- if (node.uri == this._cachedLeftPaneSelectedURI)
+ if (placeURI == this._cachedLeftPaneSelectedURI)
return;
- this._cachedLeftPaneSelectedURI = node.uri;
+ this._cachedLeftPaneSelectedURI = placeURI;
// At this point, resetSearchBox is true, because the left pane selection
// has changed; otherwise we would have returned earlier.
PlacesSearchBox.searchFilter.reset();
this._setSearchScopeForNode(node);
this.updateDetailsPane();
},
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1299,36 +1299,31 @@ PlacesTreeView.prototype = {
return props + " " + properties;
},
getColumnProperties(aColumn) { return ""; },
isContainer: function PTV_isContainer(aRow) {
// Only leaf nodes aren't listed in the rows array.
let node = this._rows[aRow];
- if (node === undefined)
+ if (node === undefined || !PlacesUtils.nodeIsContainer(node))
return false;
- if (PlacesUtils.nodeIsContainer(node)) {
- // Flat-lists may ignore expandQueries and other query options when
- // they are asked to open a container.
- if (this._flatList)
- return true;
+ // Flat-lists may ignore expandQueries and other query options when
+ // they are asked to open a container.
+ if (this._flatList)
+ return true;
- // treat non-expandable childless queries as non-containers
- if (PlacesUtils.nodeIsQuery(node)) {
- let parent = node.parent;
- if ((PlacesUtils.nodeIsQuery(parent) ||
- PlacesUtils.nodeIsFolder(parent)) &&
- !PlacesUtils.asQuery(node).hasChildren)
- return PlacesUtils.asQuery(parent).queryOptions.expandQueries;
- }
- return true;
+ // Treat non-expandable childless queries as non-containers, unless they
+ // are tags.
+ if (PlacesUtils.nodeIsQuery(node) && !PlacesUtils.nodeIsTagQuery(node)) {
+ PlacesUtils.asQuery(node);
+ return node.queryOptions.expandQueries || node.hasChildren;
}
- return false;
+ return true;
},
isContainerOpen: function PTV_isContainerOpen(aRow) {
if (this._flatList)
return false;
// All containers are listed in the rows array.
return this._rows[aRow].containerOpen;
--- a/browser/components/places/tests/browser/browser_cutting_bookmarks.js
+++ b/browser/components/places/tests/browser/browser_cutting_bookmarks.js
@@ -56,17 +56,17 @@ add_task(async function() {
var selectBookmarksIn = async function(organizer, bookmarks, aLeftPaneQuery) {
let PlacesOrganizer = organizer.PlacesOrganizer;
let ContentTree = organizer.ContentTree;
info("Selecting " + aLeftPaneQuery + " in the left pane");
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
for (let {guid} of bookmarks) {
let bookmark = await PlacesUtils.bookmarks.fetch(guid);
- is(bookmark.parentGuid, PlacesOrganizer._places.selectedNode.targetFolderGuid,
+ is(bookmark.parentGuid, PlacesUtils.getConcreteItemGuid(PlacesOrganizer._places.selectedNode),
"Bookmark has the right parent");
}
info("Selecting the bookmarks in the right pane");
ContentTree.view.selectItems(bookmarks.map(bm => bm.guid));
for (let node of ContentTree.view.selectedNodes) {
is(node.bookmarkIndex, node.title,
--- a/browser/components/places/tests/browser/browser_library_add_tags.js
+++ b/browser/components/places/tests/browser/browser_library_add_tags.js
@@ -7,17 +7,17 @@ add_task(async function test_add_tags()
const uri = "http://example.com/";
// Add a bookmark.
await PlacesUtils.bookmarks.insert({
url: uri,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
- // Open the Library and select the "UnfilledBookmarks".
+ // Open the Library on "UnfiledBookmarks".
let library = await promiseLibrary("UnfiledBookmarks");
let bookmarkNode = library.ContentTree.view.selectedNode;
Assert.equal(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark");
// Add a tag to the bookmark.
fillBookmarkTextField("editBMPanel_tagsField", "tag1", library);
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -2124,19 +2124,21 @@ nsNavBookmarks::ResultNodeForContainer(i
NS_ADDREF(*aNode);
return NS_OK;
}
nsresult
nsNavBookmarks::QueryFolderChildren(
int64_t aFolderId,
+ nsNavHistoryQueryOptions* aOriginalOptions,
nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aChildren)
{
+ NS_ENSURE_ARG_POINTER(aOriginalOptions);
NS_ENSURE_ARG_POINTER(aOptions);
NS_ENSURE_ARG_POINTER(aChildren);
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match those returned
// by mDBGetURLPageInfo, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
@@ -2157,32 +2159,34 @@ nsNavBookmarks::QueryFolderChildren(
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
NS_ENSURE_SUCCESS(rv, rv);
int32_t index = -1;
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
- rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
+ rv = ProcessFolderNodeRow(row, aOriginalOptions, aOptions, aChildren, index);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsNavBookmarks::ProcessFolderNodeRow(
mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOriginalOptions,
nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aChildren,
int32_t& aCurrentIndex)
{
NS_ENSURE_ARG_POINTER(aRow);
+ NS_ENSURE_ARG_POINTER(aOriginalOptions);
NS_ENSURE_ARG_POINTER(aOptions);
NS_ENSURE_ARG_POINTER(aChildren);
// The results will be in order of aCurrentIndex. Even if we don't add a node
// because it was excluded, we need to count its index, so do that before
// doing anything else.
aCurrentIndex++;
@@ -2223,17 +2227,17 @@ nsNavBookmarks::ProcessFolderNodeRow(
bool isNull;
rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
NS_ENSURE_SUCCESS(rv, rv);
}
- node = new nsNavHistoryFolderResultNode(title, aOptions, id);
+ node = new nsNavHistoryFolderResultNode(title, aOriginalOptions, id);
rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
NS_ENSURE_SUCCESS(rv, rv);
node->GetAsFolder()->mTargetFolderGuid = node->mBookmarkGuid;
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
reinterpret_cast<int64_t*>(&node->mDateAdded));
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -124,35 +124,41 @@ public:
nsresult ResultNodeForContainer(int64_t aID,
nsNavHistoryQueryOptions* aOptions,
nsNavHistoryResultNode** aNode);
// Find all the children of a folder, using the given query and options.
// For each child, a ResultNode is created and added to |children|.
// The results are ordered by folder position.
nsresult QueryFolderChildren(int64_t aFolderId,
+ nsNavHistoryQueryOptions* aOriginalOptions,
nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* children);
/**
* Turns aRow into a node and appends it to aChildren if it is appropriate to
* do so.
*
* @param aRow
* A Storage statement (in the case of synchronous execution) or row of
* a result set (in the case of asynchronous execution).
+ * @param aOriginalOptions
+ * The original options of the parent folder node. These are the
+ * options used to define the parent node.
* @param aOptions
- * The options of the parent folder node.
+ * The options of the parent folder node. These are the options used
+ * to fill the parent node.
* @param aChildren
* The children of the parent folder node.
* @param aCurrentIndex
* The index of aRow within the results. When called on the first row,
* this should be set to -1.
*/
nsresult ProcessFolderNodeRow(mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOriginalOptions,
nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aChildren,
int32_t& aCurrentIndex);
/**
* The async version of QueryFolderChildren.
*
* @param aNode
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -285,56 +285,16 @@ nsNavHistoryResultNode::GetResult()
return container->mResult;
}
node = node->mParent;
} while (node);
MOZ_ASSERT(false, "No container node found in hierarchy!");
return nullptr;
}
-
-/**
- * Searches up the tree for the closest ancestor node that has an options
- * structure. This will tell us the options that were used to generate this
- * node.
- *
- * Be careful, this function walks up the tree, so it can not be used when
- * result nodes are created because they have no parent. Only call this
- * function after the tree has been built.
- */
-nsNavHistoryQueryOptions*
-nsNavHistoryResultNode::GetGeneratingOptions()
-{
- if (!mParent) {
- // When we have no parent, it either means we haven't built the tree yet,
- // in which case calling this function is a bug, or this node is the root
- // of the tree. When we are the root of the tree, our own options are the
- // generating options.
- if (IsContainer())
- return GetAsContainer()->mOptions;
-
- NS_NOTREACHED("Can't find a generating node for this container, perhaps FillStats has not been called on this tree yet?");
- return nullptr;
- }
-
- // Look up the tree. We want the options that were used to create this node,
- // and since it has a parent, it's the options of an ancestor, not of the node
- // itself. So start at the parent.
- nsNavHistoryContainerResultNode* cur = mParent;
- while (cur) {
- if (cur->IsContainer())
- return cur->GetAsContainer()->mOptions;
- cur = cur->mParent;
- }
-
- // We should always find a container node as an ancestor.
- NS_NOTREACHED("Can't find a generating node for this container, the tree seemes corrupted.");
- return nullptr;
-}
-
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode,
mResult,
mChildren)
NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryContainerResultNode)
@@ -347,29 +307,33 @@ nsNavHistoryContainerResultNode::nsNavHi
nsNavHistoryQueryOptions* aOptions) :
nsNavHistoryResultNode(aURI, aTitle, 0, 0),
mResult(nullptr),
mContainerType(aContainerType),
mExpanded(false),
mOptions(aOptions),
mAsyncCanceledState(NOT_CANCELED)
{
+ MOZ_ASSERT(mOptions);
+ MOZ_ALWAYS_SUCCEEDS(mOptions->Clone(getter_AddRefs(mOriginalOptions)));
}
nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
const nsACString& aURI, const nsACString& aTitle,
PRTime aTime, uint32_t aContainerType,
nsNavHistoryQueryOptions* aOptions) :
nsNavHistoryResultNode(aURI, aTitle, 0, aTime),
mResult(nullptr),
mContainerType(aContainerType),
mExpanded(false),
mOptions(aOptions),
mAsyncCanceledState(NOT_CANCELED)
{
+ MOZ_ASSERT(mOptions);
+ MOZ_ALWAYS_SUCCEEDS(mOptions->Clone(getter_AddRefs(mOriginalOptions)));
}
nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
{
// Explicitly clean up array of children of this container. We must ensure
// all references are gone and all of their destructors are called.
mChildren.Clear();
@@ -424,18 +388,17 @@ nsNavHistoryContainerResultNode::GetCont
}
NS_IMETHODIMP
nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen)
{
if (aContainerOpen) {
if (!mExpanded) {
- nsNavHistoryQueryOptions* options = GetGeneratingOptions();
- if (options && options->AsyncEnabled())
+ if (mOptions->AsyncEnabled())
OpenContainerAsync();
else
OpenContainer();
}
}
else {
if (mExpanded)
CloseContainer();
@@ -606,37 +569,57 @@ nsNavHistoryContainerResultNode::CancelA
void
nsNavHistoryContainerResultNode::FillStats()
{
uint32_t accessCount = 0;
PRTime newTime = 0;
for (int32_t i = 0; i < mChildren.Count(); ++i) {
nsNavHistoryResultNode* node = mChildren[i];
- node->mParent = this;
- node->mIndentLevel = mIndentLevel + 1;
- if (node->IsContainer()) {
- nsNavHistoryContainerResultNode* container = node->GetAsContainer();
- container->mResult = mResult;
- container->FillStats();
- }
+ SetAsParentOfNode(node);
accessCount += node->mAccessCount;
// this is how container nodes get sorted by date
// The container gets the most recent time of the child nodes.
if (node->mTime > newTime)
newTime = node->mTime;
}
if (mExpanded) {
mAccessCount = accessCount;
if (!IsQuery() || newTime > mTime)
mTime = newTime;
}
}
+void
+nsNavHistoryContainerResultNode::SetAsParentOfNode(nsNavHistoryResultNode* aNode) {
+ aNode->mParent = this;
+ aNode->mIndentLevel = mIndentLevel + 1;
+ if (aNode->IsContainer()) {
+ nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
+ // Propagate some of the parent's options to this container.
+ if (mOptions->ExcludeItems()) {
+ container->mOptions->SetExcludeItems(true);
+ }
+ if (mOptions->ExcludeQueries()) {
+ container->mOptions->SetExcludeQueries(true);
+ }
+ if (mOptions->ExcludeReadOnlyFolders()) {
+ container->mOptions->SetExcludeReadOnlyFolders(true);
+ }
+ if (aNode->IsFolder() && mOptions->AsyncEnabled()) {
+ container->mOptions->SetAsyncEnabled(true);
+ }
+ if (!mOptions->ExpandQueries()) {
+ container->mOptions->SetExpandQueries(false);
+ }
+ container->mResult = mResult;
+ container->FillStats();
+ }
+}
/**
* This is used when one container changes to do a minimal update of the tree
* structure. When something changes, you want to call FillStats if necessary
* and update this container completely. Then call this function which will
* walk up the tree and fill in the previous containers.
*
* Note that you have to tell us by how much our access count changed. Our
@@ -1326,24 +1309,17 @@ nsNavHistoryContainerResultNode::FindChi
*/
nsresult
nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode,
int32_t aIndex)
{
nsNavHistoryResult* result = GetResult();
NS_ENSURE_STATE(result);
- aNode->mParent = this;
- aNode->mIndentLevel = mIndentLevel + 1;
- if (aNode->IsContainer()) {
- // need to update all the new item's children
- nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
- container->mResult = result;
- container->FillStats();
- }
+ SetAsParentOfNode(aNode);
if (!mChildren.InsertObjectAt(aNode, aIndex))
return NS_ERROR_OUT_OF_MEMORY;
// Update our stats and notify the result's observers.
uint32_t oldAccessCount = mAccessCount;
PRTime oldTime = mTime;
@@ -1729,17 +1705,17 @@ nsNavHistoryContainerResultNode::GetChil
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
nsNavHistoryContainerResultNode,
nsINavHistoryQueryResultNode)
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
const nsACString& aTitle, const nsACString& aQueryURI) :
nsNavHistoryContainerResultNode(aQueryURI, aTitle,
nsNavHistoryResultNode::RESULT_TYPE_QUERY,
- nullptr),
+ new nsNavHistoryQueryOptions()),
mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
mHasSearchTerms(false),
mContentsValid(false),
mBatchChanges(0)
{
}
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
@@ -1819,35 +1795,28 @@ nsNavHistoryQueryResultNode::~nsNavHisto
* Whoever made us may want non-expanding queries. However, we always expand
* when we are the root node, or else asking for non-expanding queries would be
* useless. A query node is not expandable if excludeItems is set or if
* expandQueries is unset.
*/
bool
nsNavHistoryQueryResultNode::CanExpand()
{
- if (IsContainersQuery())
+ // The root node and containersQueries can always expand;
+ if ((mResult && mResult->mRootNode == this) ||
+ IsContainersQuery()) {
return true;
-
- // If ExcludeItems is set on the root or on the node itself, don't expand.
- if ((mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
- Options()->ExcludeItems())
+ }
+
+ if (mOptions->ExcludeItems()) {
return false;
-
- // Check the ancestor container.
- nsNavHistoryQueryOptions* options = GetGeneratingOptions();
- if (options) {
- if (options->ExcludeItems())
- return false;
- if (options->ExpandQueries())
- return true;
}
-
- if (mResult && mResult->mRootNode == this)
+ if (mOptions->ExpandQueries()) {
return true;
+ }
return false;
}
/**
* Some query with a particular result type can contain other queries. They
* must be always expandable
@@ -2028,26 +1997,27 @@ nsNavHistoryQueryResultNode::GetQueryOpt
* Safe options getter, ensures queries are parsed first.
*/
nsNavHistoryQueryOptions*
nsNavHistoryQueryResultNode::Options()
{
nsresult rv = VerifyQueriesParsed();
if (NS_FAILED(rv))
return nullptr;
- NS_ASSERTION(mOptions, "Options invalid, cannot generate from URI");
+ MOZ_ASSERT(mOptions, "Options invalid, cannot generate from URI");
return mOptions;
}
nsresult
nsNavHistoryQueryResultNode::VerifyQueriesParsed()
{
if (mQueries.Count() > 0) {
- NS_ASSERTION(mOptions, "If a result has queries, it also needs options");
+ MOZ_ASSERT(mOriginalOptions && mOptions,
+ "If a result has queries, it also needs options");
return NS_OK;
}
NS_ASSERTION(!mURI.IsEmpty(),
"Query nodes must have either a URI or query/options");
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
@@ -2062,31 +2032,31 @@ nsNavHistoryQueryResultNode::VerifyQueri
nsresult
nsNavHistoryQueryResultNode::VerifyQueriesSerialized()
{
if (!mURI.IsEmpty()) {
return NS_OK;
}
- NS_ASSERTION(mQueries.Count() > 0 && mOptions,
- "Query nodes must have either a URI or query/options");
+ MOZ_ASSERT(mQueries.Count() > 0 && mOptions,
+ "Query nodes must have either a URI or query/options");
nsTArray<nsINavHistoryQuery*> flatQueries;
flatQueries.SetCapacity(mQueries.Count());
for (int32_t i = 0; i < mQueries.Count(); ++i)
flatQueries.AppendElement(static_cast<nsINavHistoryQuery*>
(mQueries.ObjectAt(i)));
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = history->QueriesToQueryString(flatQueries.Elements(),
flatQueries.Length(),
- mOptions, mURI);
+ mOriginalOptions, mURI);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(!mURI.IsEmpty());
return NS_OK;
}
nsresult
nsNavHistoryQueryResultNode::FillChildren()
@@ -2636,17 +2606,17 @@ nsNavHistoryQueryResultNode::OnDeleteURI
// way to know that without executing the child query and counting results.
nsresult rv = Refresh();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
bool onlyOneEntry = (mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_URI ||
- mOptions->ResultType() ==
+ mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMArray<nsNavHistoryResultNode> matches;
RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
for (int32_t i = 0; i < matches.Count(); ++i) {
@@ -2699,17 +2669,17 @@ nsNavHistoryQueryResultNode::OnPageChang
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
switch (aChangedAttribute) {
case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
bool onlyOneEntry = (mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_URI ||
- mOptions->ResultType() ==
+ mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
nullptr);
break;
}
default:
NS_WARNING("Unknown page changed notification");
}
@@ -2751,17 +2721,17 @@ nsNavHistoryQueryResultNode::NotifyIfTag
{
nsNavHistoryResult* result = GetResult();
NS_ENSURE_STATE(result);
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
bool onlyOneEntry = (mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_URI ||
- mOptions->ResultType() ==
+ mOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS
);
// Find matching URI nodes.
RefPtr<nsNavHistoryResultNode> node;
nsNavHistory* history = nsNavHistory::GetHistoryService();
nsCOMArray<nsNavHistoryResultNode> matches;
@@ -3112,22 +3082,24 @@ nsNavHistoryFolderResultNode::GetUri(nsA
uint32_t queryCount;
nsINavHistoryQuery** queries;
nsresult rv = GetQueries(&queryCount, &queries);
NS_ENSURE_SUCCESS(rv, rv);
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
- rv = history->QueriesToQueryString(queries, queryCount, mOptions, aURI);
+ rv = history->QueriesToQueryString(queries, queryCount, mOriginalOptions, mURI);
for (uint32_t queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
NS_RELEASE(queries[queryIndex]);
}
free(queries);
- return rv;
+ NS_ENSURE_SUCCESS(rv, rv);
+ aURI = mURI;
+ return NS_OK;
}
/**
* @return the queries that give you this bookmarks folder
*/
NS_IMETHODIMP
nsNavHistoryFolderResultNode::GetQueries(uint32_t* queryCount,
@@ -3178,17 +3150,20 @@ nsNavHistoryFolderResultNode::FillChildr
"Don't call FillChildren when contents are valid");
NS_ASSERTION(mChildren.Count() == 0,
"We are trying to fill children when there already are some");
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
// Actually get the folder children from the bookmark service.
- nsresult rv = bookmarks->QueryFolderChildren(mTargetFolderItemId, mOptions, &mChildren);
+ nsresult rv = bookmarks->QueryFolderChildren(mTargetFolderItemId,
+ mOriginalOptions,
+ mOptions,
+ &mChildren);
NS_ENSURE_SUCCESS(rv, rv);
// PERFORMANCE: it may be better to also fill any child folders at this point
// so that we can draw tree twisties without doing a separate query later.
// If we don't end up drawing twisties a lot, it doesn't matter. If we do
// this, we should wrap everything in a transaction here on the bookmark
// service's connection.
@@ -3300,17 +3275,18 @@ nsNavHistoryFolderResultNode::HandleResu
if (!bmSvc) {
CancelAsyncOpen(false);
return NS_ERROR_OUT_OF_MEMORY;
}
// Consume all the currently available rows of the result set.
nsCOMPtr<mozIStorageRow> row;
while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
- nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOptions, &mChildren,
+ nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOriginalOptions,
+ mOptions, &mChildren,
mAsyncBookmarkIndex);
if (NS_FAILED(rv)) {
CancelAsyncOpen(false);
return rv;
}
}
return NS_OK;
@@ -3543,19 +3519,17 @@ nsNavHistoryFolderResultNode::OnItemAdde
// It's possible our result registered due to a previous notification, for
// example the Library left pane could have refreshed and replaced the
// right pane as a consequence. In such a case our contents are already
// up-to-date. That's OK.
if (node)
return NS_OK;
}
- bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
- (mParent && mParent->mOptions->ExcludeItems()) ||
- mOptions->ExcludeItems();
+ bool excludeItems = mOptions->ExcludeItems();
// here, try to do something reasonable if the bookmark service gives us
// a bogus index.
if (aIndex < 0) {
NS_NOTREACHED("Invalid index for item adding: <0");
aIndex = 0;
}
else if (aIndex > mChildren.Count()) {
@@ -3661,19 +3635,18 @@ nsNavHistoryFolderResultNode::OnItemRemo
// It's possible our result registered due to a previous notification, for
// example the Library left pane could have refreshed and replaced the
// right pane as a consequence. In such a case our contents are already
// up-to-date. That's OK.
if (!node) {
return NS_OK;
}
- bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
- (mParent && mParent->mOptions->ExcludeItems()) ||
- mOptions->ExcludeItems();
+ bool excludeItems = mOptions->ExcludeItems();
+
if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
// don't update items when we aren't displaying them, but we do need to
// adjust everybody's bookmark indices to account for the removal
ReindexRange(aIndex, INT32_MAX, -1);
return NS_OK;
}
if (!StartIncrementalUpdate())
@@ -3809,20 +3782,17 @@ nsNavHistoryFolderResultNode::OnItemVisi
int64_t aVisitId,
PRTime aTime,
uint32_t aTransitionType,
nsIURI* aURI,
int64_t aParentId,
const nsACString& aGUID,
const nsACString& aParentGUID)
{
- bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
- (mParent && mParent->mOptions->ExcludeItems()) ||
- mOptions->ExcludeItems();
- if (excludeItems)
+ if (mOptions->ExcludeItems())
return NS_OK; // don't update items when we aren't displaying them
RESTART_AND_RETURN_IF_ASYNC_PENDING();
if (!StartIncrementalUpdate())
return NS_OK;
uint32_t nodeIndex;
@@ -3909,19 +3879,17 @@ nsNavHistoryFolderResultNode::OnItemMove
// example the Library left pane could have refreshed and replaced the
// right pane as a consequence. In such a case our contents are already
// up-to-date. That's OK.
if (node && aNewParent == mTargetFolderItemId && index == static_cast<uint32_t>(aNewIndex))
return NS_OK;
if (!node && aOldParent == mTargetFolderItemId)
return NS_OK;
- bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
- (mParent && mParent->mOptions->ExcludeItems()) ||
- mOptions->ExcludeItems();
+ bool excludeItems = mOptions->ExcludeItems();
if (node && excludeItems && (node->IsURI() || node->IsSeparator())) {
// Don't update items when we aren't displaying them.
return NS_OK;
}
if (!StartIncrementalUpdate())
return NS_OK; // entire container was refreshed for us
@@ -4531,18 +4499,17 @@ nsNavHistoryResult::OnItemChanged(int64_
for (uint32_t i = 0; i < list->Length(); ++i) {
RefPtr<nsNavHistoryFolderResultNode> folder = list->ElementAt(i);
if (folder) {
uint32_t nodeIndex;
RefPtr<nsNavHistoryResultNode> node =
folder->FindChildById(aItemId, &nodeIndex);
// if ExcludeItems is true we don't update non visible items
- bool excludeItems = (mRootNode->mOptions->ExcludeItems()) ||
- folder->mOptions->ExcludeItems();
+ bool excludeItems = folder->mOptions->ExcludeItems();
if (node &&
(!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
folder->StartIncrementalUpdate()) {
node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
aNewValue, aLastModified, aItemType, aParentId,
aGUID, aParentGUID, aOldValue, aSource);
}
}
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -291,17 +291,16 @@ public:
uint16_t aSource);
protected:
virtual ~nsNavHistoryResultNode() {}
public:
nsNavHistoryResult* GetResult();
- nsNavHistoryQueryOptions* GetGeneratingOptions();
// These functions test the type. We don't use a virtual function since that
// would take a vtable slot for every one of (potentially very many) nodes.
// Note that GetType() already has a vtable slot because its on the iface.
bool IsTypeContainer(uint32_t type) {
return type == nsINavHistoryResultNode::RESULT_TYPE_QUERY ||
type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER ||
type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT;
@@ -466,19 +465,28 @@ public:
// When there are children, this stores the open state in the tree
// this is set to the default in the constructor.
bool mExpanded;
// Filled in by the result type generator in nsNavHistory.
nsCOMArray<nsNavHistoryResultNode> mChildren;
+ // mOriginalOptions is the options object used to _define_ this specific
+ // container node. It may differ from mOptions, that is the options used
+ // to _fill_ this container node, because mOptions may be modified by
+ // the direct parent of this container node, see SetAsParentOfNode. For
+ // example, if the parent has excludeItems, options will have it too, even if
+ // originally this object was not defined with that option.
+ nsCOMPtr<nsNavHistoryQueryOptions> mOriginalOptions;
nsCOMPtr<nsNavHistoryQueryOptions> mOptions;
void FillStats();
+ // Sets this container as parent of aNode, propagating the appropriate options.
+ void SetAsParentOfNode(nsNavHistoryResultNode* aNode);
nsresult ReverseUpdateStats(int32_t aAccessCountChange);
// Sorting methods.
typedef nsCOMArray<nsNavHistoryResultNode>::nsCOMArrayComparatorFunc SortComparator;
virtual uint16_t GetSortType();
virtual void GetSortingAnnotation(nsACString& aSortingAnnotation);
static SortComparator GetSortingComparator(uint16_t aSortType);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/queries/test_options_inherit.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests inheritance of certain query options like:
+ * excludeItems, excludeQueries, expandQueries.
+ */
+
+"use strict";
+
+add_task(async function() {
+ await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.unfiledGuid,
+ children: [
+ {
+ title: "folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ children: [
+ { title: "query",
+ url: "place:queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
+ },
+ { title: "bm",
+ url: "http://example.com",
+ },
+ ]
+ },
+ { title: "bm",
+ url: "http://example.com",
+ },
+ ]
+ });
+
+ await test_query({}, 2, 2, 3);
+ await test_query({ expandQueries: false }, 2, 2, 0);
+ await test_query({ excludeItems: true }, 1, 1, 0);
+ await test_query({ excludeItems: true, expandQueries: false }, 1, 1, 0);
+ await test_query({ excludeItems: true, excludeQueries: true }, 1, 0, 0);
+});
+
+
+async function test_query(opts, expectedRootCc, expectedFolderCc, expectedQueryCc) {
+ let query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.unfiledBookmarksFolderId], 1);
+ let options = PlacesUtils.history.getNewQueryOptions();
+ for (const [o, v] of Object.entries(opts)) {
+ info(`setting ${o} to ${v}`);
+ options[o] = v;
+ }
+ let root = PlacesUtils.history.executeQuery(query, options).root;
+ root.containerOpen = true;
+
+ Assert.equal(root.childCount, expectedRootCc, "Checking root child count");
+ if (root.childCount > 0) {
+ let folder = root.getChild(0);
+ Assert.equal(folder.title, "folder", "Found the expected folder");
+ PlacesUtils.asContainer(folder).containerOpen = true;
+ Assert.equal(folder.childCount, expectedFolderCc, "Checking folder child count");
+ if (folder.childCount) {
+ let placeQuery = folder.getChild(0);
+ PlacesUtils.asQuery(placeQuery).containerOpen = true;
+ Assert.equal(placeQuery.childCount, expectedQueryCc, "Checking query child count");
+ placeQuery.containerOpen = false;
+ }
+ folder.containerOpen = false;
+ }
+ root.containerOpen = false;
+}
--- a/toolkit/components/places/tests/queries/xpcshell.ini
+++ b/toolkit/components/places/tests/queries/xpcshell.ini
@@ -6,16 +6,17 @@ skip-if = toolkit == 'android'
[test_abstime-annotation-domain.js]
[test_abstime-annotation-uri.js]
[test_async.js]
skip-if = (os == 'win' && ccov) # Bug 1423667
[test_containersQueries_sorting.js]
[test_history_queries_tags_liveUpdate.js]
[test_history_queries_titles_liveUpdate.js]
[test_onlyBookmarked.js]
+[test_options_inherit.js]
[test_queryMultipleFolder.js]
[test_querySerialization.js]
[test_redirects.js]
[test_results-as-tag-contents-query.js]
[test_results-as-visit.js]
[test_search_tags.js]
[test_searchterms-domain.js]
[test_searchterms-uri.js]