Bug 384228 - Implement Search-In-Folder(s) back-end. r=dietrich.

This commit is contained in:
mozilla.mano@sent.com 2007-09-15 13:07:02 -07:00
Родитель b312070289
Коммит 6c8d54ddfb
4 изменённых файлов: 374 добавлений и 133 удалений

Просмотреть файл

@ -12,15 +12,17 @@
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla History System
* The Original Code is Mozilla History System.
*
* The Initial Developer of the Original Code is
* Google Inc.
* 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):
* Brett Wilson <brettw@gmail.com> (original author)
* Dietrich Ayala <dietrich@mozilla.com>
* Seth Spitzer <sspitzer@mozilla.com>
* Asaf Romano <mano@mozilla.com>
*
* 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
@ -180,7 +182,8 @@ static PRBool IsNumericHostName(const nsCString& aHost);
static PRInt64 GetSimpleBookmarksQueryFolder(
const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsNavHistoryQueryOptions* aOptions);
static void ParseSearchQuery(const nsString& aQuery, nsStringArray* aTerms);
static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsTArray<nsStringArray*>* aTerms);
inline void ReverseString(const nsString& aInput, nsAString& aReversed)
{
@ -1552,8 +1555,10 @@ nsNavHistory::EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries
// an array.
nsCOMArray<nsNavHistoryResultNode> inputSet;
inputSet.AppendObject(aNode);
nsCOMArray<nsNavHistoryQuery> queries;
queries.AppendObject(query);
nsCOMArray<nsNavHistoryResultNode> filteredSet;
nsresult rv = FilterResultSet(nsnull, inputSet, &filteredSet, query->SearchTerms());
nsresult rv = FilterResultSet(nsnull, inputSet, &filteredSet, queries);
if (NS_FAILED(rv))
continue;
if (! filteredSet.Count())
@ -2384,6 +2389,17 @@ nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
// bind parameters
PRInt32 numParameters = 0;
// optimize the case where we just want a list with no grouping: this
// directly fills in the results and we avoid a copy of the whole list
PRBool resultAsList = PR_TRUE;
PRUint32 groupCount;
const PRUint16 *groupings = aOptions->GroupingMode(&groupCount);
if (groupCount != 0 || aOptions->ExcludeQueries()) {
resultAsList = PR_FALSE;
}
PRInt32 i;
for (i = 0; i < aQueries.Count(); i ++) {
PRInt32 clauseParameters = 0;
@ -2391,18 +2407,20 @@ nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
aQueries[i], aOptions, &clauseParameters);
NS_ENSURE_SUCCESS(rv, rv);
numParameters += clauseParameters;
if (resultAsList) {
if (aQueries[i]->Folders().Length() != 0) {
resultAsList = PR_FALSE;
} else {
PRBool hasSearchTerms;
rv = aQueries[i]->GetHasSearchTerms(&hasSearchTerms);
if (hasSearchTerms)
resultAsList = PR_FALSE;
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
PRUint32 groupCount;
const PRUint16 *groupings = aOptions->GroupingMode(&groupCount);
PRBool hasSearchTerms;
rv = aQueries[0]->GetHasSearchTerms(&hasSearchTerms);
NS_ENSURE_SUCCESS(rv, rv);
if (groupCount == 0 && ! hasSearchTerms) {
// optimize the case where we just want a list with no grouping: this
// directly fills in the results and we avoid a copy of the whole list
if (resultAsList) {
rv = ResultsAsList(statement, aOptions, aResults);
NS_ENSURE_SUCCESS(rv, rv);
} else {
@ -2411,22 +2429,13 @@ nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
rv = ResultsAsList(statement, aOptions, &toplevel);
NS_ENSURE_SUCCESS(rv, rv);
if (hasSearchTerms) {
// keyword search
if (groupCount == 0) {
// keyword search with no grouping: can filter directly into the result
FilterResultSet(aResultNode, toplevel, aResults, aQueries[0]->SearchTerms());
} else {
// keyword searching with grouping: need intermediate filtered results
nsCOMArray<nsNavHistoryResultNode> filteredResults;
FilterResultSet(aResultNode, toplevel, &filteredResults, aQueries[0]->SearchTerms());
rv = RecursiveGroup(aResultNode, filteredResults, groupings, groupCount,
aResults);
NS_ENSURE_SUCCESS(rv, rv);
}
if (groupCount == 0) {
FilterResultSet(aResultNode, toplevel, aResults, aQueries);
} else {
// group unfiltered results
rv = RecursiveGroup(aResultNode, toplevel, groupings, groupCount, aResults);
nsCOMArray<nsNavHistoryResultNode> filteredResults;
FilterResultSet(aResultNode, toplevel, &filteredResults, aQueries);
rv = RecursiveGroup(aResultNode, filteredResults, groupings, groupCount,
aResults);
NS_ENSURE_SUCCESS(rv, rv);
}
}
@ -3603,19 +3612,8 @@ nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const
}
if (aOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS) {
// Folders only have an affect on bookmark queries
// XXX: add multiple folders support
if (aQuery->Folders().Length() == 1) {
if (!aClause->IsEmpty())
*aClause += NS_LITERAL_CSTRING(" AND ");
nsCAutoString paramString;
parameterString(aStartParameter + *aParamCount, paramString);
(*aParamCount) ++;
*aClause += NS_LITERAL_CSTRING(" b.parent = ") + paramString;
}
} else if (aQuery->OnlyBookmarked()) {
if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
aQuery->OnlyBookmarked()) {
// only bookmarked, has no affect on bookmarks-only queries
if (!aClause->IsEmpty())
*aClause += NS_LITERAL_CSTRING(" AND ");
@ -3754,18 +3752,6 @@ nsNavHistory::BindQueryClauseParameters(mozIStorageStatement* statement,
(*aParamCount) ++;
}
// folder
if (aOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS) {
// XXX: add multiple folders support
if (aQuery->Folders().Length() == 1) {
rv = statement->BindInt64Parameter(aStartParameter + *aParamCount,
aQuery->Folders()[0]);
NS_ENSURE_SUCCESS(rv, rv);
(*aParamCount) ++;
}
}
// onlyBookmarked: nothing to bind
// domain (see GetReversedHostname for more info on reversed host names)
if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) {
nsString revDomain;
@ -4143,104 +4129,140 @@ nsNavHistory::URIHasTag(nsIURI* aURI, const nsAString& aTag)
//
// This does some post-query-execution filtering:
// - searching on title & url
// - parent folder (recursively)
// - excludeQueries
nsresult
nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aParentNode,
nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode,
const nsCOMArray<nsNavHistoryResultNode>& aSet,
nsCOMArray<nsNavHistoryResultNode>* aFiltered,
const nsString& aSearch)
const nsCOMArray<nsNavHistoryQuery>& aQueries)
{
nsresult rv;
nsStringArray terms;
ParseSearchQuery(aSearch, &terms);
// get the bookmarks service
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
// parse the search terms
nsTArray<nsStringArray*> terms;
ParseSearchTermsFromQueries(aQueries, &terms);
PRUint32 queryIndex;
// The includeFolders array for each query is initialized with its
// query's folders array. We add sub-folders as we check items.
nsTArray< nsTArray<PRInt64>* > includeFolders;
nsTArray< nsTArray<PRInt64>* > excludeFolders;
for (queryIndex = 0;
queryIndex < aQueries.Count(); queryIndex++) {
includeFolders.AppendElement(new nsTArray<PRInt64>(aQueries[queryIndex]->Folders()));
excludeFolders.AppendElement(new nsTArray<PRInt64>());
}
// filter against query options
// XXX only excludeQueries is supported at the moment
PRBool excludeQueries = PR_FALSE;
if (aParentNode) {
rv = aParentNode->mOptions->GetExcludeQueries(&excludeQueries);
if (aQueryNode) {
rv = aQueryNode->mOptions->GetExcludeQueries(&excludeQueries);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCStringArray searchAnnotations;
/*
if (mAnnotationService) {
searchAnnotations.AppendCString(NS_LITERAL_CSTRING("qwer"));
searchAnnotations.AppendCString(NS_LITERAL_CSTRING("asdf"));
searchAnnotations.AppendCString(NS_LITERAL_CSTRING("zxcv"));
//mAnnotationService->GetSearchableAnnotations();
}
*/
for (PRInt32 nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex ++) {
if (aParentNode && aParentNode->mItemId != -1) {
if (aParentNode->mItemId == aSet[nodeIndex]->mItemId) {
continue; // filter out bookmark nodes that are the same as the parent
}
}
if (excludeQueries && IsQueryURI(aSet[nodeIndex]->mURI))
continue;
PRBool allTermsFound = PR_TRUE;
nsStringArray curAnnotations;
/*
if (searchAnnotations.Count()) {
// come up with a list of all annotation *values* we need to search
for (PRInt32 annotIndex = 0; annotIndex < searchAnnotations.Count(); annotIndex ++) {
nsString annot;
if (NS_SUCCEEDED(mAnnotationService->GetAnnotationString(
aSet[nodeIndex]->mURI,
*searchAnnotations[annotIndex],
annot)))
curAnnotations.AppendString(annot);
}
PRInt64 parentId = -1;
if (aSet[nodeIndex]->mItemId != -1) {
if (aQueryNode->mItemId == aSet[nodeIndex]->mItemId)
continue;
rv = bookmarks->GetFolderIdForItem(aSet[nodeIndex]->mItemId, &parentId);
NS_ENSURE_SUCCESS(rv, rv);
}
*/
if (terms.Count() == 0) {
allTermsFound = PR_TRUE;
} else {
// Append the node if it matches one of the queries
PRBool appendNode = PR_FALSE;
for (queryIndex = 0;
queryIndex < aQueries.Count() && !appendNode; queryIndex++) {
// parent folder
if (includeFolders[queryIndex]->Length() != 0) {
// filter out simple history nodes from bookmark queries
if (aSet[nodeIndex]->mItemId == -1)
continue;
for (PRInt32 termIndex = 0; termIndex < terms.Count(); termIndex ++) {
// filter out the node of which their parent is in the exclude-folders
// cache
if (excludeFolders[queryIndex]->IndexOf(parentId) != -1)
continue;
if (includeFolders[queryIndex]->IndexOf(parentId) == -1) {
// check ancestors
PRInt64 ancestor = parentId, lastAncestor;
PRBool belongs = PR_FALSE;
while (!belongs) {
// Avoid using |ancestor| itself if GetFolderIdForItem failed.
lastAncestor = ancestor;
// GetFolderIdForItems throws when called for the places-root
if (NS_FAILED(bookmarks->GetFolderIdForItem(ancestor,&ancestor))) {
break;
} else if (includeFolders[queryIndex]->IndexOf(ancestor) != -1) {
belongs = PR_TRUE;
}
}
if (belongs) {
includeFolders[queryIndex]->AppendElement(lastAncestor);
} else {
excludeFolders[queryIndex]->AppendElement(lastAncestor);
continue;
}
}
}
// search terms
// XXXmano/dietrich: when bug 331487 is fixed, bookmark queries can group
// by folder or not regardless of specified folders or search terms.
PRBool allTermsFound = PR_TRUE;
for (PRInt32 termIndex = 0; termIndex < terms[queryIndex]->Count() &&
allTermsFound; termIndex ++) {
// search terms should match title, url or tags
PRBool termFound = PR_FALSE;
// title and URL
if (CaseInsensitiveFindInReadable(*terms[termIndex],
if (CaseInsensitiveFindInReadable(*terms[queryIndex]->StringAt(termIndex),
NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mTitle)) ||
(aSet[nodeIndex]->IsURI() &&
CaseInsensitiveFindInReadable(*terms[termIndex],
NS_ConvertUTF8toUTF16(aSet[nodeIndex]->mURI))))
CaseInsensitiveFindInReadable(*terms[queryIndex]->StringAt(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;
}
}*/
// search tags
// tags
if (!termFound) {
nsCOMPtr<nsIURI> itemURI;
rv = NS_NewURI(getter_AddRefs(itemURI), aSet[nodeIndex]->mURI);
NS_ENSURE_SUCCESS(rv, rv);
termFound = URIHasTag(itemURI, *terms[termIndex]);
termFound = URIHasTag(itemURI, *terms[queryIndex]->StringAt(termIndex));
}
if (!termFound) {
if (!termFound)
allTermsFound = PR_FALSE;
break;
}
}
}
if (!allTermsFound)
continue;
if (allTermsFound)
appendNode = PR_TRUE;
}
if (appendNode)
aFiltered->AppendObject(aSet[nodeIndex]);
}
// de-allocate the matrixes
for (PRUint32 i=0; i < aQueries.Count(); i++) {
delete terms[i];
delete includeFolders[i];
delete excludeFolders[i];
}
return NS_OK;
}
@ -4920,9 +4942,13 @@ GetSimpleBookmarksQueryFolder(const nsCOMArray<nsNavHistoryQuery>& aQueries,
}
// ParseSearchQuery
// ParseSearchTermsFromQueries
//
// This just breaks the query up into words. We don't do anything fancy,
// Construct a matrix of search terms from the given queries array.
// All of the query objects are ORed together. Within a query, all the terms
// are ANDed together. See nsINavHistory.idl.
//
// This just breaks the quer up into words. We don't do anything fancy,
// not even quoting. We do, however, strip quotes, because people might
// try to input quotes expecting them to do something and get no results
// back.
@ -4932,26 +4958,38 @@ inline PRBool isQueryWhitespace(PRUnichar ch)
return ch == ' ';
}
void ParseSearchQuery(const nsString& aQuery, nsStringArray* aTerms)
void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
nsTArray<nsStringArray*>* aTerms)
{
PRInt32 lastBegin = -1;
for (PRUint32 i = 0; i < aQuery.Length(); i ++) {
if (isQueryWhitespace(aQuery[i]) || aQuery[i] == '"') {
if (lastBegin >= 0) {
// found the end of a word
aTerms->AppendString(Substring(aQuery, lastBegin, i - lastBegin));
lastBegin = -1;
}
} else {
if (lastBegin < 0) {
// found the beginning of a word
lastBegin = i;
for (PRUint32 i=0; i < aQueries.Count(); i++) {
nsStringArray *queryTerms = new nsStringArray();
PRBool hasSearchTerms;
if (NS_SUCCEEDED(aQueries[i]->GetHasSearchTerms(&hasSearchTerms)) &&
hasSearchTerms) {
const nsString& searchTerms = aQueries[i]->SearchTerms();
for (PRUint32 j = 0; j < searchTerms.Length(); j++) {
if (isQueryWhitespace(searchTerms[j]) ||
searchTerms[j] == '"') {
if (lastBegin >= 0) {
// found the end of a word
queryTerms->AppendString(Substring(searchTerms, lastBegin,
j - lastBegin));
lastBegin = -1;
}
} else {
if (lastBegin < 0) {
// found the beginning of a word
lastBegin = j;
}
}
}
// last word
if (lastBegin >= 0)
queryTerms->AppendString(Substring(searchTerms, lastBegin));
}
aTerms->AppendElement(queryTerms);
}
// last word
if (lastBegin >= 0)
aTerms->AppendString(Substring(aQuery, lastBegin));
}

Просмотреть файл

@ -516,7 +516,7 @@ protected:
nsresult FilterResultSet(nsNavHistoryQueryResultNode *aParentNode,
const nsCOMArray<nsNavHistoryResultNode>& aSet,
nsCOMArray<nsNavHistoryResultNode>* aFiltered,
const nsString& aSearch);
const nsCOMArray<nsNavHistoryQuery>& aQueries);
// observers
nsMaybeWeakPtrArray<nsINavHistoryObserver> mObservers;

Просмотреть файл

@ -0,0 +1,97 @@
/* -*- 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):
* Seth Spitzer <sspitzer@mozilla.com>
* Asaf Romano <mano@mozilla.com>
*
* 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 bookmarks root id
var root = bmsvc.bookmarksRoot;
// main
function run_test() {
// test querying for bookmarks in multiple folders
var testFolder1 = bmsvc.createFolder(root, "bug 384228 test folder 1",
bmsvc.DEFAULT_INDEX);
var testFolder2 = bmsvc.createFolder(root, "bug 384228 test folder 2",
bmsvc.DEFAULT_INDEX);
var testFolder3 = bmsvc.createFolder(root, "bug 384228 test folder 3",
bmsvc.DEFAULT_INDEX);
var b1 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"),
bmsvc.DEFAULT_INDEX, "title b1 (folder 1)");
var b2 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"),
bmsvc.DEFAULT_INDEX, "title b2 (folder 1)");
var b3 = bmsvc.insertBookmark(testFolder2, uri("http://foo.tld/"),
bmsvc.DEFAULT_INDEX, "title b3 (folder 2)");
var b4 = bmsvc.insertBookmark(testFolder3, uri("http://foo.tld/"),
bmsvc.DEFAULT_INDEX, "title b4 (folder 3)");
// also test recursive search
var testFolder1_1 = bmsvc.createFolder(testFolder1, "bug 384228 test folder 1.1",
bmsvc.DEFAULT_INDEX);
var b5 = bmsvc.insertBookmark(testFolder1_1, uri("http://a1.com/"),
bmsvc.DEFAULT_INDEX, "title b5 (folder 1.1)");
var options = histsvc.getNewQueryOptions();
var query = histsvc.getNewQuery();
query.searchTerms = "title";
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
query.setFolders([testFolder1, testFolder2], 2);
var result = histsvc.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
// should not match item from folder 3
do_check_eq(rootNode.childCount, 4);
do_check_eq(rootNode.getChild(0).itemId, b1);
do_check_eq(rootNode.getChild(1).itemId, b2);
do_check_eq(rootNode.getChild(2).itemId, b3);
do_check_eq(rootNode.getChild(3).itemId, b5);
}

Просмотреть файл

@ -0,0 +1,106 @@
/* -*- 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 bug 395593 unit test code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Asaf Romano <mano@mozilla.com> (Original Author)
*
* 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 bookmarks root id
var root = bmsvc.bookmarksRoot;
// main
function run_test() {
/**
* All of the query objects are ORed together. Within a query, all the terms
* are ANDed together. See nsINavHistory.idl.
*/
var id1 = bmsvc.insertBookmark(root, uri("http://foo.tld"),
bmsvc.DEFAULT_INDEX, "123 0");
var id2 = bmsvc.insertBookmark(root, uri("http://foo.tld"),
bmsvc.DEFAULT_INDEX, "456");
var id3 = bmsvc.insertBookmark(root, uri("http://foo.tld"),
bmsvc.DEFAULT_INDEX, "123 456");
var id4 = bmsvc.insertBookmark(root, uri("http://foo.tld"),
bmsvc.DEFAULT_INDEX, "789 456");
var queries = [];
queries.push(histsvc.getNewQuery());
queries[0].searchTerms = "123";
queries.push(histsvc.getNewQuery());
queries[1].searchTerms = "789";
var options = histsvc.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
var result = histsvc.executeQueries(queries, queries.length, options);
var root = result.root;
root.containerOpen = true;
do_check_eq(root.childCount, 3);
do_check_eq(root.getChild(0).itemId, id1);
do_check_eq(root.getChild(1).itemId, id3);
do_check_eq(root.getChild(2).itemId, id4);
queries[0].searchTerms = "123";
queries[1].searchTerms = "456";
result = histsvc.executeQueries(queries, queries.length, options);
root = result.root;
root.containerOpen = true;
do_check_eq(root.childCount, 4);
do_check_eq(root.getChild(0).itemId, id1);
do_check_eq(root.getChild(1).itemId, id2);
do_check_eq(root.getChild(2).itemId, id3);
do_check_eq(root.getChild(3).itemId, id4);
queries[0].searchTerms = "00";
queries[1].searchTerms = "789";
result = histsvc.executeQueries(queries, queries.length, options);
root = result.root;
root.containerOpen = true;
do_check_eq(root.childCount, 1);
do_check_eq(root.getChild(0).itemId, id4);
}