From 49382c7e2e4bef5b805c1ffac282727c50e4fd70 Mon Sep 17 00:00:00 2001 From: "dietrich%mozilla.com" Date: Sat, 1 Sep 2007 21:23:36 +0000 Subject: [PATCH] Bug 317847 Implement "Save this Search" (r=mano) --- browser/components/places/content/places.js | 74 +++++- browser/components/places/content/places.xul | 3 +- .../places/content/placesOverlay.xul | 8 +- .../chrome/browser/places/places.properties | 4 + .../components/places/src/nsNavHistory.cpp | 85 +++--- toolkit/components/places/src/nsNavHistory.h | 6 +- .../places/src/nsNavHistoryQuery.cpp | 4 +- .../components/places/src/nsNavHistoryQuery.h | 2 +- .../places/src/nsNavHistoryResult.cpp | 36 ++- .../places/tests/bookmarks/test_bookmarks.js | 3 +- .../tests/bookmarks/test_savedsearches.js | 250 ++++++++++++++++++ 11 files changed, 412 insertions(+), 63 deletions(-) create mode 100644 toolkit/components/places/tests/bookmarks/test_savedsearches.js diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js index 970e294fa70..7b50a2368a8 100755 --- a/browser/components/places/content/places.js +++ b/browser/components/places/content/places.js @@ -192,10 +192,6 @@ var PlacesOrganizer = { // Items are only excluded on the left pane var options = node.queryOptions.clone(); options.excludeItems = false; - // Unset excludeQueries so incremental update is enabled for the content - // pane. - // XXXmano: remove that once we unset excludeQueries for the left pane. - options.excludeQueries = false; this._content.load(queries, OptionsFilter.filter(queries, options, null)); @@ -343,6 +339,57 @@ var PlacesOrganizer = { } gEditItemOverlay.uninitPanel(); deck.selectedIndex = 0; + }, + + /** + * Save the current search (or advanced query) to the bookmarks root. + */ + saveSearch: function PP_saveSearch() { + // Get the place: uri for the query. + // If the advanced query builder is showing, use that. + var queries = []; + var options = this.getCurrentOptions(); + options.excludeQueries = true; + var advancedSearch = document.getElementById("advancedSearch"); + if (!advancedSearch.collapsed) { + queries = PlacesQueryBuilder.queries; + } + // If not, use the value of the search box. + else if (PlacesSearchBox.value && PlacesSearchBox.value.length > 0) { + var query = PlacesUtils.history.getNewQuery(); + query.searchTerms = PlacesSearchBox.value; + queries.push(query); + } + // if there is no query, do nothing. + else { + // XXX should probably have a dialog here to explain that the user needs to search first. + return; + } + var placeSpec = PlacesUtils.history.queriesToQueryString(queries, + queries.length, + options); + var placeURI = IO.newURI(placeSpec); + + // Prompt the user for a name for the query. + // XXX - using prompt service for now; will need to make + // a real dialog and localize when we're sure this is the UI we want. + var title = PlacesUtils.getString("saveSearch.title"); + var inputLabel = PlacesUtils.getString("saveSearch.inputLabel"); + var defaultText = PlacesUtils.getString("saveSearch.defaultText"); + + var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Ci.nsIPromptService); + var check = {value: false}; + var input = {value: defaultText}; + var save = prompts.prompt(null, title, inputLabel, input, null, check); + + // Don't add the query if the user cancels or clears the seach name. + if (!save || input.value == "") + return; + + // Add the place: uri as a bookmark under the places root. + var txn = PlacesUtils.ptm.createItem(placeURI, PlacesUtils.bookmarks.bookmarksRoot, PlacesUtils.bookmarks.DEFAULT_INDEX, input.value); + PlacesUtils.ptm.commitTransaction(txn); } }; @@ -384,8 +431,10 @@ var PlacesSearchBox = { PO.setHeaderText(PO.HEADER_TYPE_SEARCH, filterString); break; case "bookmarks": - if (filterString != "") + if (filterString) { content.applyFilter(filterString, true); + PO.setHeaderText(PO.HEADER_TYPE_SEARCH, filterString); + } else PlacesOrganizer.onPlaceSelected(); break; @@ -445,6 +494,8 @@ var PlacesSearchBox = { }, set filterCollection(collectionName) { this.searchFilter.setAttribute("collection", collectionName); + if (this.searchFilter.value) + return; // don't overwrite pre-existing search terms var newGrayText = null; if (collectionName == "collection") newGrayText = PlacesOrganizer._places.selectedNode.title; @@ -484,6 +535,9 @@ var PlacesSearchBox = { */ var PlacesQueryBuilder = { + queries: [], + queryOptions: null, + _numRows: 0, /** @@ -906,7 +960,7 @@ var PlacesQueryBuilder = { doSearch: function PQB_doSearch() { // Create the individual queries. var queryType = document.getElementById("advancedSearchType").selectedItem.value; - var queries = []; + this.queries = []; if (queryType == "and") queries.push(PlacesUtils.history.getNewQuery()); var updated = 0; @@ -922,7 +976,7 @@ var PlacesQueryBuilder = { // If they're being OR-ed, add a separate query for each row. var query; if (queryType == "and") - query = queries[0]; + query = this.queries[0]; else query = PlacesUtils.history.getNewQuery(); @@ -930,15 +984,15 @@ var PlacesQueryBuilder = { this._queryBuilders[querySubject](query, prefix); if (queryType == "or") - queries.push(query); + this.queries.push(query); ++updated; } } // Make sure we're getting uri results, not visits - var options = PlacesOrganizer.getCurrentOptions(); - options.resultType = options.RESULT_TYPE_URI; + this.options = PlacesOrganizer.getCurrentOptions(); + this.options.resultType = options.RESULT_TYPE_URI; // XXXben - find some public way of doing this! PlacesOrganizer._content.load(queries, diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul index 52851647a97..ddbb7773500 100755 --- a/browser/components/places/content/places.xul +++ b/browser/components/places/content/places.xul @@ -86,7 +86,8 @@ oncommand="PlacesOrganizer.exportBookmarks();"/> - + diff --git a/browser/components/places/content/placesOverlay.xul b/browser/components/places/content/placesOverlay.xul index 7f59f78d7b1..715dbe70ae1 100644 --- a/browser/components/places/content/placesOverlay.xul +++ b/browser/components/places/content/placesOverlay.xul @@ -155,25 +155,25 @@ command="cmd_cut" label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" - selection="separator|link|folder|mixed" + selection="separator|link|folder|mixed|query" forcehideselection="livemarkChild"/> + selection="separator|link|folder|query"/> + selection="mutable|query"/> & aQueries nsCOMArray inputSet; inputSet.AppendObject(aNode); nsCOMArray filteredSet; - nsresult rv = FilterResultSet(inputSet, &filteredSet, query->SearchTerms()); + nsresult rv = FilterResultSet(nsnull, inputSet, &filteredSet, query->SearchTerms()); if (NS_FAILED(rv)) continue; if (! filteredSet.Count()) @@ -2412,11 +2412,11 @@ nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode, // keyword search if (groupCount == 0) { // keyword search with no grouping: can filter directly into the result - FilterResultSet(toplevel, aResults, aQueries[0]->SearchTerms()); + FilterResultSet(aResultNode, toplevel, aResults, aQueries[0]->SearchTerms()); } else { // keyword searching with grouping: need intermediate filtered results nsCOMArray filteredResults; - FilterResultSet(toplevel, &filteredResults, aQueries[0]->SearchTerms()); + FilterResultSet(aResultNode, toplevel, &filteredResults, aQueries[0]->SearchTerms()); rv = RecursiveGroup(aResultNode, filteredResults, groupings, groupCount, aResults); NS_ENSURE_SUCCESS(rv, rv); @@ -4096,21 +4096,26 @@ nsNavHistory::GroupByHost(nsNavHistoryQueryResultNode *aResultNode, // nsNavHistory::FilterResultSet // -// Currently, this just does title/url filtering. This should be expanded in -// the future. +// This does some post-query-execution filtering: +// - searching on title & url +// - excludeQueries nsresult -nsNavHistory::FilterResultSet(const nsCOMArray& aSet, +nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aParentNode, + const nsCOMArray& aSet, nsCOMArray* aFiltered, const nsString& aSearch) { + nsresult rv; nsStringArray terms; ParseSearchQuery(aSearch, &terms); - // if there are no search terms, just return everything (i.e. do nothing) - if (terms.Count() == 0) { - aFiltered->AppendObjects(aSet); - return NS_OK; + // filter against query options + // XXX only excludeQueries is supported at the moment + PRBool excludeQueries = PR_FALSE; + if (aParentNode) { + rv = aParentNode->mOptions->GetExcludeQueries(&excludeQueries); + NS_ENSURE_SUCCESS(rv, rv); } nsCStringArray searchAnnotations; @@ -4124,6 +4129,14 @@ nsNavHistory::FilterResultSet(const nsCOMArray& aSet, */ for (PRInt32 nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex ++) { + if (aParentNode) { + if (aParentNode->mItemId == aSet[nodeIndex]->mItemId) + continue; // filter out nodes that are the same as the parent + } + + if (excludeQueries && IsQueryURI(aSet[nodeIndex]->mURI)) + continue; + PRBool allTermsFound = PR_TRUE; nsStringArray curAnnotations; @@ -4141,28 +4154,33 @@ nsNavHistory::FilterResultSet(const nsCOMArray& aSet, } */ - for (PRInt32 termIndex = 0; termIndex < terms.Count(); termIndex ++) { - PRBool termFound = PR_FALSE; - // title and URL - if (CaseInsensitiveFindInReadable(*terms[termIndex], - NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mTitle)) || - (aSet[nodeIndex]->IsURI() && - CaseInsensitiveFindInReadable(*terms[termIndex], - NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mURI)))) - termFound = PR_TRUE; - // searchable annotations - /*if (! termFound) { - for (PRInt32 annotIndex = 0; annotIndex < curAnnotations.Count(); annotIndex ++) { - if (CaseInsensitiveFindInReadable(*terms[termIndex], - *curAnnotations[annotIndex])) - termFound = PR_TRUE; + if (terms.Count() == 0) { + allTermsFound = PR_TRUE; + } else { + for (PRInt32 termIndex = 0; termIndex < terms.Count(); termIndex ++) { + PRBool termFound = PR_FALSE; + // title and URL + if (CaseInsensitiveFindInReadable(*terms[termIndex], + NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mTitle)) || + (aSet[nodeIndex]->IsURI() && + CaseInsensitiveFindInReadable(*terms[termIndex], + NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mURI)))) + termFound = PR_TRUE; + // searchable annotations + /*if (! termFound) { + for (PRInt32 annotIndex = 0; annotIndex < curAnnotations.Count(); annotIndex ++) { + if (CaseInsensitiveFindInReadable(*terms[termIndex], + *curAnnotations[annotIndex])) + termFound = PR_TRUE; + } + }*/ + if (! termFound) { + allTermsFound = PR_FALSE; + break; } - }*/ - if (! termFound) { - allTermsFound = PR_FALSE; - break; } } + if (allTermsFound) aFiltered->AppendObject(aSet[nodeIndex]); } @@ -4318,9 +4336,8 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow, if (IsQueryURI(url)) { // special case "place:" URIs: turn them into containers - // XXX: should we set the bookmark identifier for this sort of nodes? It - // would sure break few assumption on the frontend side - return QueryRowToResult(url, title, accessCount, time, favicon, aResult); + PRInt64 itemId = aRow->AsInt64(kGetInfoIndex_ItemId); + return QueryRowToResult(itemId, url, title, accessCount, time, favicon, aResult); } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI) { *aResult = new nsNavHistoryResultNode(url, title, accessCount, time, favicon); @@ -4387,7 +4404,8 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow, // folder or query node. nsresult -nsNavHistory::QueryRowToResult(const nsACString& aURI, const nsACString& aTitle, +nsNavHistory::QueryRowToResult(PRInt64 itemId, const nsACString& aURI, + const nsACString& aTitle, PRUint32 aAccessCount, PRTime aTime, const nsACString& aFavicon, nsNavHistoryResultNode** aNode) @@ -4422,6 +4440,7 @@ nsNavHistory::QueryRowToResult(const nsACString& aURI, const nsACString& aTitle, queries, options); if (! *aNode) return NS_ERROR_OUT_OF_MEMORY; + (*aNode)->mItemId = itemId; NS_ADDREF(*aNode); } } diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index a007d841430..792a0745ae0 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -249,7 +249,8 @@ public: nsresult RowToResult(mozIStorageValueArray* aRow, nsNavHistoryQueryOptions* aOptions, nsNavHistoryResultNode** aResult); - nsresult QueryRowToResult(const nsACString& aURI, const nsACString& aTitle, + nsresult QueryRowToResult(PRInt64 aItemId, const nsACString& aURI, + const nsACString& aTitle, PRUint32 aAccessCount, PRTime aTime, const nsACString& aFavicon, nsNavHistoryResultNode** aNode); @@ -510,7 +511,8 @@ protected: nsCOMArray* aDest, PRBool aIsDomain); - nsresult FilterResultSet(const nsCOMArray& aSet, + nsresult FilterResultSet(nsNavHistoryQueryResultNode *aParentNode, + const nsCOMArray& aSet, nsCOMArray* aFiltered, const nsString& aSearch); diff --git a/toolkit/components/places/src/nsNavHistoryQuery.cpp b/toolkit/components/places/src/nsNavHistoryQuery.cpp index a80a88a2208..008a36327ac 100644 --- a/toolkit/components/places/src/nsNavHistoryQuery.cpp +++ b/toolkit/components/places/src/nsNavHistoryQuery.cpp @@ -470,9 +470,9 @@ nsNavHistory::QueriesToQueryString(nsINavHistoryQuery **aQueries, } // expand queries - if (options->ExpandQueries()) { + if (!options->ExpandQueries()) { AppendAmpersandIfNonempty(queryString); - queryString += NS_LITERAL_CSTRING(QUERYKEY_EXPAND_QUERIES "=1"); + queryString += NS_LITERAL_CSTRING(QUERYKEY_EXPAND_QUERIES "=0"); } // include hidden diff --git a/toolkit/components/places/src/nsNavHistoryQuery.h b/toolkit/components/places/src/nsNavHistoryQuery.h index ddaccb39014..f7ea217ee6e 100644 --- a/toolkit/components/places/src/nsNavHistoryQuery.h +++ b/toolkit/components/places/src/nsNavHistoryQuery.h @@ -122,7 +122,7 @@ public: mExcludeItems(PR_FALSE), mExcludeQueries(PR_FALSE), mExcludeReadOnlyFolders(PR_FALSE), - mExpandQueries(PR_FALSE), + mExpandQueries(PR_TRUE), mIncludeHidden(PR_FALSE), mShowSessions(PR_FALSE), mMaxResults(0), diff --git a/toolkit/components/places/src/nsNavHistoryResult.cpp b/toolkit/components/places/src/nsNavHistoryResult.cpp index ef4aab8fc2c..2b7cf7c663d 100644 --- a/toolkit/components/places/src/nsNavHistoryResult.cpp +++ b/toolkit/components/places/src/nsNavHistoryResult.cpp @@ -1894,14 +1894,19 @@ nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode( // // 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. +// queries would be useless. A query node is not expandable if excludeItems=1 +// or expandQueries=0. PRBool nsNavHistoryQueryResultNode::CanExpand() { nsNavHistoryQueryOptions* options = GetGeneratingOptions(); - if (options && options->ExpandQueries()) - return PR_TRUE; + if (options) { + if (options->ExcludeItems()) + return PR_FALSE; + if (options->ExpandQueries()) + return PR_TRUE; + } if (mResult && mResult->mRootNode == this) return PR_TRUE; return PR_FALSE; @@ -1939,11 +1944,11 @@ nsNavHistoryQueryResultNode::OnRemoving() nsresult nsNavHistoryQueryResultNode::OpenContainer() { - NS_ASSERTION(! mExpanded, "Container must be expanded to close it"); + NS_ASSERTION(!mExpanded, "Container must be closed to open it"); mExpanded = PR_TRUE; - if (! CanExpand()) + if (!CanExpand()) return NS_OK; - if (! mContentsValid) { + if (!mContentsValid) { nsresult rv = FillChildren(); NS_ENSURE_SUCCESS(rv, rv); } @@ -2568,7 +2573,7 @@ nsNavHistoryQueryResultNode::OnItemChanged(PRInt64 aItemId, if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) return Refresh(); else - NS_NOTREACHED("history observers should not get OnItemChanged, but should get the corresponding history notifications instead"); + NS_WARNING("history observers should not get OnItemChanged, but should get the corresponding history notifications instead"); return NS_OK; } @@ -3050,8 +3055,21 @@ nsNavHistoryFolderResultNode::OnItemAdded(PRInt64 aItemId, nsresult rv = bookmarks->GetItemType(aItemId, &itemType); NS_ENSURE_SUCCESS(rv, rv); + // check for query URIs, which are bookmarks, but treated as containers + // in results and views. + PRBool isQuery = PR_FALSE; + if (itemType == nsINavBookmarksService::TYPE_BOOKMARK) { + nsCOMPtr itemURI; + rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsCAutoString itemURISpec; + rv = itemURI->GetSpec(itemURISpec); + NS_ENSURE_SUCCESS(rv, rv); + isQuery = IsQueryURI(itemURISpec); + } + if (itemType != nsINavBookmarksService::TYPE_FOLDER && - mOptions->ExcludeItems()) { + !isQuery && mOptions->ExcludeItems()) { // don't update items when we aren't displaying them, but we still need // to adjust bookmark indices to account for the insertion ReindexRange(aIndex, PR_INT32_MAX, 1); @@ -3517,7 +3535,7 @@ nsNavHistoryResult::AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode) mIsAllBookmarksObserver = PR_TRUE; } if (mAllBookmarksObservers.IndexOf(aNode) != mAllBookmarksObservers.NoIndex) { - NS_NOTREACHED("Attempting to register an observer twice!"); + NS_WARNING("Attempting to register an observer twice!"); return; } mAllBookmarksObservers.AppendElement(aNode); diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks.js b/toolkit/components/places/tests/bookmarks/test_bookmarks.js index 21192aaf168..6a5089bb21a 100644 --- a/toolkit/components/places/tests/bookmarks/test_bookmarks.js +++ b/toolkit/components/places/tests/bookmarks/test_bookmarks.js @@ -443,7 +443,8 @@ function run_test() { var node = rootNode.getChild(i); if (node.type == node.RESULT_TYPE_FOLDER || node.type == node.RESULT_TYPE_URI || - node.type == node.RESULT_TYPE_SEPARATOR) { + node.type == node.RESULT_TYPE_SEPARATOR || + node.type == node.RESULT_TYPE_QUERY) { do_check_true(node.itemId > 0); } else { diff --git a/toolkit/components/places/tests/bookmarks/test_savedsearches.js b/toolkit/components/places/tests/bookmarks/test_savedsearches.js new file mode 100644 index 00000000000..43163477430 --- /dev/null +++ b/toolkit/components/places/tests/bookmarks/test_savedsearches.js @@ -0,0 +1,250 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * Dietrich Ayala + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Get bookmark service +try { + var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService); +} catch(ex) { + do_throw("Could not get nav-bookmarks-service\n"); +} + +// Get history service +try { + var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService); +} catch(ex) { + do_throw("Could not get history service\n"); +} + +// Get annotation service +try { + var annosvc= Cc["@mozilla.org/browser/annotation-service;1"].getService(Ci.nsIAnnotationService); +} catch(ex) { + do_throw("Could not get annotation service\n"); +} + +// Get global history service +try { + var bhist = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIBrowserHistory); +} catch(ex) { + do_throw("Could not get history service\n"); +} + +// get bookmarks root id +var root = bmsvc.bookmarksRoot; + +// main +function run_test() { + // a search term that matches a default bookmark + var searchTerm = "about"; + + // create a folder to hold all the tests + // this makes the tests more tolerant of changes to the default bookmarks set + // also, name it using the search term, for testing that containers that match don't show up in query results + var testRoot = bmsvc.createFolder(root, searchTerm, bmsvc.DEFAULT_INDEX); + + /****************************************** + * saved searches - bookmarks + ******************************************/ + + // add a bookmark that matches the search term + var bookmarkId = bmsvc.insertBookmark(root, uri("http://foo.com"), bmsvc.DEFAULT_INDEX, searchTerm); + + // create a saved-search that matches a default bookmark + var searchId = bmsvc.insertBookmark(testRoot, + uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=1"), + bmsvc.DEFAULT_INDEX, searchTerm); + + // query for the test root, expandQueries=0 + // the query should show up as a regular bookmark + try { + var options = histsvc.getNewQueryOptions(); + options.expandQueries = 0; + var query = histsvc.getNewQuery(); + query.setFolders([testRoot], 1); + var result = histsvc.executeQuery(query, options); + var rootNode = result.root; + rootNode.containerOpen = true; + var cc = rootNode.childCount; + do_check_eq(cc, 1); + for (var i = 0; i < cc; i++) { + var node = rootNode.getChild(i); + // test that queries have valid itemId + do_check_true(node.itemId > 0); + // test that the container is closed + node.QueryInterface(Ci.nsINavHistoryContainerResultNode); + do_check_eq(node.containerOpen, false); + } + } + catch(ex) { + do_throw("expandQueries=0 query error: " + ex); + } + + // bookmark saved search + // query for the test root, expandQueries=1 + // the query should show up as a query container, with 1 child + try { + var options = histsvc.getNewQueryOptions(); + options.expandQueries = 1; + var query = histsvc.getNewQuery(); + query.setFolders([testRoot], 1); + var result = histsvc.executeQuery(query, options); + var rootNode = result.root; + rootNode.containerOpen = true; + var cc = rootNode.childCount; + do_check_eq(cc, 1); + for (var i = 0; i < cc; i++) { + var node = rootNode.getChild(i); + // test that query node type is container when expandQueries=1 + do_check_eq(node.type, node.RESULT_TYPE_QUERY); + // test that queries (as containers) have valid itemId + do_check_true(node.itemId > 0); + node.QueryInterface(Ci.nsINavHistoryContainerResultNode); + node.containerOpen = true; + + // test that queries have children when excludeItems=1 + // test that query nodes don't show containers (shouldn't have our folder that matches) + // test that queries don't show themselves in query results (shouldn't have our saved search) + do_check_eq(node.childCount, 1); + + // test that bookmark shows in query results + var item = node.getChild(0); + do_check_eq(item.itemId, bookmarkId); + + // XXX - FAILING - test live-update of query results - add a bookmark that matches the query + //var tmpBmId = bmsvc.insertBookmark(root, uri("http://" + searchTerm + ".com"), bmsvc.DEFAULT_INDEX, searchTerm + "blah"); + //do_check_eq(query.childCount, 2); + + // XXX - test live-update of query results - delete a bookmark that matches the query + //bmsvc.removeItem(tmpBMId); + //do_check_eq(query.childCount, 1); + + // test live-update of query results - add a folder that matches the query + bmsvc.createFolder(root, searchTerm + "zaa", bmsvc.DEFAULT_INDEX); + do_check_eq(node.childCount, 1); + // test live-update of query results - add a query that matches the query + bmsvc.insertBookmark(root, uri("place:terms=foo&excludeQueries=1&expandQueries=1&queryType=1"), + bmsvc.DEFAULT_INDEX, searchTerm + "blah"); + do_check_eq(node.childCount, 1); + } + } + catch(ex) { + do_throw("expandQueries=1 bookmarks query: " + ex); + } + + // delete the bookmark search + bmsvc.removeItem(searchId); + + /****************************************** + * saved searches - history + ******************************************/ + + // add a visit that matches the search term + var testURI = uri("http://" + searchTerm + ".com"); + bhist.addPageWithDetails(testURI, searchTerm, Date.now()); + + // create a saved-search that matches the visit we added + var searchId = bmsvc.insertBookmark(testRoot, + uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=0"), + bmsvc.DEFAULT_INDEX, searchTerm); + + // query for the test root, expandQueries=1 + // the query should show up as a query container, with 1 child + try { + var options = histsvc.getNewQueryOptions(); + options.expandQueries = 1; + var query = histsvc.getNewQuery(); + query.setFolders([testRoot], 1); + var result = histsvc.executeQuery(query, options); + var rootNode = result.root; + rootNode.containerOpen = true; + var cc = rootNode.childCount; + do_check_eq(cc, 1); + for (var i = 0; i < cc; i++) { + var node = rootNode.getChild(i); + // test that query node type is container when expandQueries=1 + do_check_eq(node.type, node.RESULT_TYPE_QUERY); + // test that queries (as containers) have valid itemId + do_check_eq(node.itemId, searchId); + node.QueryInterface(Ci.nsINavHistoryContainerResultNode); + node.containerOpen = true; + + // test that queries have children when excludeItems=1 + // test that query nodes don't show containers (shouldn't have our folder that matches) + // test that queries don't show themselves in query results (shouldn't have our saved search) + do_check_eq(node.childCount, 1); + + // test that history visit shows in query results + var item = node.getChild(0); + do_check_eq(item.type, item.RESULT_TYPE_URI); + do_check_eq(item.itemId, -1); // history visit + do_check_eq(item.uri, testURI.spec); // history visit + + // test live-update of query results - add a history visit that matches the query + bhist.addPageWithDetails(uri("http://foo.com"), searchTerm + "blah", Date.now()); + do_check_eq(node.childCount, 2); + + // test live-update of query results - delete a history visit that matches the query + bhist.removePage(uri("http://foo.com")); + do_check_eq(node.childCount, 1); + } + + // test live-update of moved queries + var tmpFolderId = bmsvc.createFolder(testRoot, "foo", bmsvc.DEFAULT_INDEX); + bmsvc.moveItem(searchId, tmpFolderId, bmsvc.DEFAULT_INDEX); + var tmpFolderNode = rootNode.getChild(0); + do_check_eq(tmpFolderNode.itemId, tmpFolderId); + tmpFolderNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + tmpFolderNode.containerOpen = true; + do_check_eq(tmpFolderNode.childCount, 1); + + // test live-update of renamed queries + bmsvc.setItemTitle(searchId, "foo"); + do_check_eq(tmpFolderNode.title, "foo"); + + // test live-update of deleted queries + bmsvc.removeItem(searchId); + try { + var tmpFolderNode = root.getChild(1); + do_throw("query was not removed"); + } catch(ex) {} + } + catch(ex) { + do_throw("expandQueries=1 bookmarks query: " + ex); + } +}