Bug 1633620 - Stop using URLs to search address books. r=mkmelin

--HG--
extra : rebase_source : 993e1a7bf994c92d077864870fb82d9ef5304e88
extra : histedit_source : 53104a72fe4bdfc227f0c3a47216f710e12cbeff
This commit is contained in:
Geoff Lankow 2020-04-06 21:52:53 +12:00
Родитель daae6cfd8f
Коммит 09fa8a614f
32 изменённых файлов: 944 добавлений и 512 удалений

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

@ -525,7 +525,7 @@ function AbDelete() {
directory.deleteCards(cardArray);
}
}
SetAbView(kAllDirectoryRoot + "?");
SetAbView();
} else {
// Delete cards from address books or mailing lists.
gAbView.deleteSelectedCards();

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

@ -224,16 +224,17 @@ function onEnterInSearchBar() {
}
let searchURI = getSelectedDirectoryURI();
let searchQuery;
let searchInput = document.getElementById("peopleSearchInput");
// Use helper method to split up search query to multi-word search
// query against multiple fields.
if (searchInput) {
let searchWords = getSearchTokens(searchInput.value);
searchURI += generateQueryURI(gQueryURIFormat, searchWords);
searchQuery = generateQueryURI(gQueryURIFormat, searchWords);
}
SetAbView(searchURI);
SetAbView(searchURI, searchQuery);
}
/**

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

@ -30,6 +30,8 @@
<script src="chrome://messenger/content/addressbook/abCommon.js"/>
<script src="chrome://messenger/content/addressbook/abResultsPane.js"/>
<script src="chrome://messenger/content/addressbook/abContactsPanel.js"/>
<script src="chrome://messenger/content/jsTreeView.js"/>
<script src="chrome://messenger/content/addressbook/abView.js"/>
<commandset id="CommandUpdate_AddressBook"
commandupdater="true"

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

@ -145,6 +145,9 @@ var gAddressBookAbViewListener = {
ResultsPaneSelectionChanged();
},
onCountChanged(total) {
// For some unknown reason the tree needs this before the changes show up.
// The view is already gAbView but setting it again works.
gAbResultsTree.view = gAbView;
SetStatusText(total);
window.dispatchEvent(new CustomEvent("countchange"));
},
@ -737,6 +740,7 @@ function onEnterInSearchBar() {
}
let searchURI = getSelectedDirectoryURI();
let searchQuery;
if (!searchURI) {
return;
}
@ -751,7 +755,7 @@ function onEnterInSearchBar() {
// query against multiple fields.
if (searchInput) {
let searchWords = getSearchTokens(searchInput.value);
searchURI += generateQueryURI(gQueryURIFormat, searchWords);
searchQuery = generateQueryURI(gQueryURIFormat, searchWords);
}
if (searchURI == kAllDirectoryRoot) {
@ -765,7 +769,7 @@ function onEnterInSearchBar() {
!gDirectoryTreeView.hasRemoteAB || searchURI != kAllDirectoryRoot + "?"
);
SetAbView(searchURI);
SetAbView(searchURI, searchQuery);
// XXX todo
// this works for synchronous searches of local addressbooks,

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

@ -46,6 +46,7 @@
<script src="chrome://messenger/content/button-menu-button.js"/>
<script src="chrome://messenger/content/jsTreeView.js"/>
<script src="chrome://messenger/content/addressbook/abTrees.js"/>
<script src="chrome://messenger/content/addressbook/abView.js"/>
<script src="chrome://messenger/content/accountUtils.js"/>
<script src="chrome://messenger/content/mailCore.js"/>
<script src="chrome://messenger/content/addressbook/addressbook.js"/>

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

@ -9,6 +9,7 @@ prefs =
subsuite = thunderbird
tags = addrbook
[browser_contact_tree.js]
[browser_directory_tree.js]
[browser_ldap_search.js]
support-files = ../../../../../mailnews/addrbook/test/unit/data/ldap_contacts.json

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

@ -0,0 +1,240 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var { toXPCOMArray } = ChromeUtils.import(
"resource:///modules/iteratorUtils.jsm"
);
var { mailTestUtils } = ChromeUtils.import(
"resource://testing-common/mailnews/MailTestUtils.jsm"
);
function createAddressBook(name) {
let dirPrefId = MailServices.ab.newAddressBook(name, "", 101);
return MailServices.ab.getDirectoryFromId(dirPrefId);
}
function createContact(firstName, lastName) {
let contact = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
Ci.nsIAbCard
);
contact.displayName = `${firstName} ${lastName}`;
contact.firstName = firstName;
contact.lastName = lastName;
contact.primaryEmail = `${firstName}.${lastName}@invalid`;
return contact;
}
function createMailingList(name) {
let list = Cc["@mozilla.org/addressbook/directoryproperty;1"].createInstance(
Ci.nsIAbDirectory
);
list.isMailList = true;
list.dirName = name;
return list;
}
var observer = {
topics: [
"addrbook-directory-created",
"addrbook-directory-updated",
"addrbook-directory-deleted",
"addrbook-contact-created",
"addrbook-contact-updated",
"addrbook-contact-deleted",
"addrbook-list-created",
"addrbook-list-updated",
"addrbook-list-deleted",
"addrbook-list-member-added",
"addrbook-list-member-removed",
],
setUp() {
for (let topic of this.topics) {
Services.obs.addObserver(observer, topic);
}
},
cleanUp() {
for (let topic of this.topics) {
Services.obs.removeObserver(observer, topic);
}
},
promiseNotification() {
return new Promise(resolve => {
this.notificationPromise = resolve;
});
},
resolveNotificationPromise() {
if (this.notificationPromise) {
let resolve = this.notificationPromise;
delete this.notificationPromise;
resolve();
}
},
notifications: [],
observe(subject, topic, data) {
info([topic, subject, data]);
this.notifications.push([topic, subject, data]);
this.resolveNotificationPromise();
},
};
add_task(async () => {
function openRootDirectory() {
mailTestUtils.treeClick(EventUtils, abWindow, abDirTree, 0, 0, {});
}
function openDirectory(directory) {
for (let i = 0; i < abDirTree.view.rowCount; i++) {
abDirTree.changeOpenState(i, true);
}
let row = abWindow.gDirectoryTreeView.getIndexForId(directory.URI);
mailTestUtils.treeClick(EventUtils, abWindow, abDirTree, row, 0, {});
}
function deleteRowWithPrompt(row) {
let promptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
mailTestUtils.treeClick(EventUtils, abWindow, abContactTree, row, 0, {});
EventUtils.synthesizeKey("VK_DELETE", {}, abWindow);
return promptPromise;
}
function checkRows(...expectedCards) {
abContactTree.view.QueryInterface(Ci.nsIAbView);
Assert.equal(
abContactTree.view.rowCount,
expectedCards.length,
"rowCount correct"
);
for (let i = 0; i < expectedCards.length; i++) {
if (expectedCards[i].isMailList) {
Assert.equal(
abContactTree.view.getCardFromRow(i).displayName,
expectedCards[i].dirName
);
} else {
Assert.equal(
abContactTree.view.getCardFromRow(i).displayName,
expectedCards[i].displayName
);
}
}
}
let bookA = createAddressBook("book A");
let contactA1 = bookA.addCard(createContact("contact", "A1"));
let bookB = createAddressBook("book B");
let contactB1 = bookB.addCard(createContact("contact", "B1"));
let abWindow = await openAddressBookWindow();
let abDirTree = abWindow.GetDirTree();
let abContactTree = abWindow.document.getElementById("abResultsTree");
observer.setUp();
openRootDirectory();
checkRows(contactA1, contactB1);
// While in bookA, add a contact and list. Check that they show up.
openDirectory(bookA);
checkRows(contactA1);
let contactA2 = bookA.addCard(createContact("contact", "A2")); // Add A2.
checkRows(contactA1, contactA2);
let listC = bookA.addMailList(createMailingList("list C")); // Add C.
// Adding a mailing list changes the view. Go back to where we were.
openDirectory(bookA);
checkRows(contactA1, contactA2, listC);
listC.addCard(contactA1);
checkRows(contactA1, contactA2, listC);
openRootDirectory();
checkRows(contactA1, contactA2, contactB1, listC);
// While in listC, add a member and remove a member. Check that they show up
// or disappear as appropriate.
openDirectory(listC);
checkRows(contactA1);
listC.addCard(contactA2);
checkRows(contactA1, contactA2);
await deleteRowWithPrompt(0);
checkRows(contactA2);
openRootDirectory();
checkRows(contactA1, contactA2, contactB1, listC);
// While in bookA, delete a contact. Check it disappears.
openDirectory(bookA);
checkRows(contactA1, contactA2, listC);
await deleteRowWithPrompt(0); // Delete A1.
checkRows(contactA2, listC);
// Now do some things in an unrelated book. Check nothing changes here.
let contactB2 = bookB.addCard(createContact("contact", "B2")); // Add B2.
checkRows(contactA2, listC);
let listD = bookB.addMailList(createMailingList("list D")); // Add D.
// Adding a mailing list changes the view. Go back to where we were.
openDirectory(bookA);
checkRows(contactA2, listC);
listD.addCard(contactB1);
checkRows(contactA2, listC);
openRootDirectory();
checkRows(contactA2, contactB1, contactB2, listC, listD);
// While in listC, do some things in an unrelated list. Check nothing
// changes here.
openDirectory(listC);
checkRows(contactA2);
listD.addCard(contactB2);
checkRows(contactA2);
listD.deleteCards(toXPCOMArray([contactB1], Ci.nsIMutableArray));
checkRows(contactA2);
bookB.deleteCards(toXPCOMArray([contactB1], Ci.nsIMutableArray));
checkRows(contactA2);
openRootDirectory();
checkRows(contactA2, contactB2, listC, listD);
// While in bookA, do some things in an unrelated book. Check nothing
// changes here.
openDirectory(bookA);
checkRows(contactA2, listC);
bookB.deleteDirectory(listD); // Delete D.
// Removing a mailing list changes the view. Go back to where we were.
openDirectory(bookA);
checkRows(contactA2, listC);
await deleteRowWithPrompt(1); // Delete C.
checkRows(contactA2);
// While in "All Address Books", make some changes and check that things
// appear or disappear as appropriate.
openRootDirectory();
checkRows(contactA2, contactB2);
let listE = bookB.addMailList(createMailingList("list E")); // Add E.
// Adding a mailing list changes the view. Go back to where we were.
openRootDirectory();
checkRows(contactA2, contactB2, listE);
listE.addCard(contactB2);
checkRows(contactA2, contactB2, listE);
listE.deleteCards(toXPCOMArray([contactB2], Ci.nsIMutableArray));
checkRows(contactA2, contactB2, listE);
bookB.deleteDirectory(listE); // Delete E.
// Removing a mailing list changes the view. Go back to where we were.
openRootDirectory();
checkRows(contactA2, contactB2);
await deleteRowWithPrompt(1);
checkRows(contactA2);
bookA.deleteCards(toXPCOMArray([contactA2], Ci.nsIMutableArray));
checkRows();
abWindow.close();
let deletePromise = observer.promiseNotification();
MailServices.ab.deleteAddressBook(bookA.URI);
await deletePromise;
deletePromise = observer.promiseNotification();
MailServices.ab.deleteAddressBook(bookB.URI);
await deletePromise;
observer.cleanUp();
});

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

@ -285,9 +285,12 @@ add_task(async () => {
`address book ("${inputs.abName}") is displayed in the address book list`
);
// Expand the tree to reveal the mailing list.
global.dirTreeClick(2, 1);
EventUtils.sendKey("RETURN", global.abWindow);
// Expand the tree to reveal the mailing list. It might already be expanded
// if earlier tests ran (which is a bug), so check first.
if (global.dirTree.view.rowCount == 4) {
global.dirTreeClick(2, 1);
EventUtils.sendKey("RETURN", global.abWindow);
}
is(
global.dirTree.view.getCellText(3, global.dirTree.columns[0]),

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

@ -35,7 +35,8 @@ add_task(async () => {
is(resultsTree.view.rowCount, expectedCards.length, "rowCount correct");
for (let i = 0; i < expectedCards.length; i++) {
is(
resultsTree.view.getCardFromRow(i).displayName,
resultsTree.view.QueryInterface(Ci.nsIAbView).getCardFromRow(i)
.displayName,
expectedCards[i].displayName,
`row ${i} has the right contact`
);

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

@ -263,6 +263,7 @@ add_task(function test_deleting_contacts_causes_confirm_prompt() {
select_address_book(addrBook1);
let totalEntries = abController.window.gAbView.rowCount;
Assert.equal(totalEntries, 4);
// Set the mock prompt to return false, so that the
// contact should not be deleted.

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

@ -347,7 +347,7 @@ var abDirTreeObserver = {
}
if (srcURI == kAllDirectoryRoot + "?") {
SetAbView(srcURI);
SetAbView();
}
document.getElementById("statusText").label = cardsTransferredText;

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

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from ../../../mail/components/addrbook/content/abCommon.js */
/* import-globals-from abView.js */
/* globals GetAbViewListener */
// gCurFrame is SeaMonkey-only */
/* globals gCurFrame */
@ -41,7 +42,7 @@ var gAbView = null;
// set up by SetAbView.
var gAbResultsTree = null;
function SetAbView(aURI) {
function SetAbView(aURI, aSearchQuery) {
// If we don't have a URI, just clear the view and leave everything else
// alone.
if (!aURI) {
@ -75,36 +76,16 @@ function SetAbView(aURI) {
}
}
var directory = GetDirectoryFromURI(aURI);
if (!directory && aURI.startsWith("moz-abdirectory://?")) {
// This is an obsolete reference to the root directory, which isn't a thing
// any more. Fortunately all we need is a way to get the URI to gAbView, so
// we can pretend we have a real directory.
directory = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIAbDirectory]),
get URI() {
return aURI;
},
};
}
if (!gAbView) {
gAbView = Cc["@mozilla.org/addressbook/abview;1"].createInstance(
Ci.nsIAbView
);
}
var actualSortColumn = gAbView.setView(
directory,
gAbView = gAbResultsTree.view = new ABView(
GetDirectoryFromURI(aURI),
aSearchQuery,
GetAbViewListener(),
sortColumn,
sortDirection
);
gAbResultsTree.view = gAbView.QueryInterface(Ci.nsITreeView);
).QueryInterface(Ci.nsITreeView);
window.dispatchEvent(new CustomEvent("viewchange"));
UpdateSortIndicators(actualSortColumn, sortDirection);
UpdateSortIndicators(sortColumn, sortDirection);
// If the selected address book is LDAP and the search box is empty,
// inform the user of the empty results pane.
@ -113,7 +94,7 @@ function SetAbView(aURI) {
let blankResultsPaneMessageBox = document.getElementById(
"blankResultsPaneMessageBox"
);
if (aURI.startsWith("moz-abldapdirectory://") && !aURI.includes("?")) {
if (aURI.startsWith("moz-abldapdirectory://") && !aSearchQuery) {
if (abResultsTree) {
abResultsTree.hidden = true;
}
@ -137,9 +118,7 @@ function SetAbView(aURI) {
}
function CloseAbView() {
if (gAbView) {
gAbView.clearView();
}
gAbView = gAbResultsTree.view = null;
}
function GetOneOrMoreCardsSelected() {
@ -242,7 +221,7 @@ function GetSelectedAbCards() {
}
}
if (!abView) {
if (!abView || !abView.selection) {
return [];
}
@ -387,6 +366,11 @@ function UpdateSortIndicators(colID, sortDirection) {
if (currCol != sortedColumn && currCol.localName == "treecol") {
currCol.removeAttribute("sortDirection");
}
// Change the column header's border colour to force it to redraw.
// Otherwise redrawing doesn't happen until something else causes it to.
currCol.style.borderColor = currCol.style.borderColor
? null
: "transparent";
currCol = currCol.nextElementSibling;
}
}
@ -426,6 +410,8 @@ var ResultsPaneController = {
if (gAbView && gAbView.selection) {
if (gAbView.directory) {
enabled = !gAbView.directory.readOnly;
} else {
enabled = true;
}
numSelected = gAbView.selection.count;
} else {

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

@ -0,0 +1,234 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals MailServices, PROTO_TREE_VIEW, Services */
var { toXPCOMArray } = ChromeUtils.import(
"resource:///modules/iteratorUtils.jsm"
);
function ABView(directory, searchQuery, listener, sortColumn, sortDirection) {
this.__proto__.__proto__ = new PROTO_TREE_VIEW();
this.directory = directory;
this.listener = listener;
let directories = directory ? [directory] : MailServices.ab.directories;
if (searchQuery) {
searchQuery = searchQuery.replace(/^\?+/, "");
for (let dir of directories) {
dir.search(searchQuery, this);
}
} else {
for (let dir of directories) {
for (let card of dir.childCards) {
this._rowMap.push(new abViewCard(card));
}
}
if (this.listener) {
this.listener.onCountChanged(this.rowCount);
}
}
this.sortBy(sortColumn, sortDirection);
}
ABView.prototype = {
QueryInterface: ChromeUtils.generateQI([
Ci.nsITreeView,
Ci.nsIAbView,
Ci.nsIAbDirSearchListener,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
_notifications: [
"addrbook-contact-created",
"addrbook-contact-deleted",
"addrbook-list-member-added",
"addrbook-list-member-removed",
],
// nsITreeView
selectionChanged() {
if (this.listener) {
this.listener.onSelectionChanged();
}
},
setTree(tree) {
this.tree = tree;
for (let topic of this._notifications) {
if (tree) {
Services.obs.addObserver(this, topic, true);
} else {
Services.obs.removeObserver(this, topic);
}
}
},
// nsIAbView
deleteSelectedCards() {
let directoryMap = new Map();
for (let i = 0; i < this.selection.getRangeCount(); i++) {
let start = {},
finish = {};
this.selection.getRangeAt(i, start, finish);
for (let j = start.value; j <= finish.value; j++) {
let card = this.getCardFromRow(j);
let directoryId = card.directoryId.split("&")[0];
let cardSet = directoryMap.get(directoryId);
if (!cardSet) {
cardSet = new Set();
directoryMap.set(directoryId, cardSet);
}
cardSet.add(card);
}
}
for (let [directoryId, cardSet] of directoryMap) {
let directory;
if (this.directory && this.directory.isMailList) {
// Removes cards from the list instead of deleting them.
directory = this.directory;
} else {
directory = MailServices.ab.getDirectoryFromId(directoryId);
}
cardSet = [...cardSet];
directory.deleteCards(
toXPCOMArray(
cardSet.filter(card => !card.isMailList),
Ci.nsIMutableArray
)
);
for (let card of cardSet.filter(card => card.isMailList)) {
MailServices.ab.deleteAddressBook(card.mailListURI);
}
}
},
getCardFromRow(row) {
return this._rowMap[row] ? this._rowMap[row].card : null;
},
sortColumn: "",
sortDirection: "",
sortBy(sortColumn, sortDirection, resort) {
if (sortColumn == this.sortColumn && !resort) {
if (sortDirection == this.sortDirection) {
return;
}
this._rowMap.reverse();
} else {
this._rowMap.sort((a, b) => {
let aText = a.getText(sortColumn);
let bText = b.getText(sortColumn);
if (aText == bText) {
return 0;
}
return aText < bText ? -1 : 1;
});
}
if (this.tree) {
this.tree.invalidate();
}
this.sortColumn = sortColumn;
this.sortDirection = sortDirection;
},
// nsIAbDirSearchListener
onSearchFoundCard(card) {
this._rowMap.push(new abViewCard(card));
},
onSearchFinished(result, errorMsg) {
this.sortBy(this.sortColumn, this.sortDirection, true);
if (this.listener) {
this.listener.onCountChanged(this.rowCount);
}
},
// nsIObserver
observe(subject, topic, data) {
if (this.directory && this.directory.UID != data) {
// How did we get here?
return;
}
switch (topic) {
case "addrbook-list-member-added":
if (!this.directory) {
break;
}
// Falls through.
case "addrbook-contact-created":
this._rowMap.push(new abViewCard(subject));
if (this.listener) {
this.listener.onCountChanged(this.rowCount);
}
break;
case "addrbook-list-member-removed":
if (!this.directory) {
break;
}
// Falls through.
case "addrbook-contact-deleted":
for (let i = this._rowMap.length - 1; i >= 0; i--) {
if (this._rowMap[i].card.equals(subject)) {
this._rowMap.splice(i, 1);
}
}
if (this.listener) {
this.listener.onCountChanged(this.rowCount);
}
break;
}
},
};
function abViewCard(card) {
this.card = card;
}
abViewCard.prototype = {
getText(columnID) {
try {
switch (columnID) {
case "addrbook": {
let { directoryId } = this.card;
return directoryId.substring(directoryId.indexOf("&") + 1);
}
case "GeneratedName":
return this.card.generateName(
Services.prefs.getIntPref("mail.addr_book.lastnamefirst", 0)
);
case "_PhoneticName":
return this.card.generatePhoneticName(true);
case "ChatName":
return this.card.isMailList ? "" : this.card.generateChatName();
default:
return this.card.isMailList
? ""
: this.card.getPropertyAsAString(columnID);
}
} catch (ex) {
return "";
}
},
get id() {
return this.card.UID;
},
get open() {
return false;
},
get level() {
return 0;
},
get children() {
return [];
},
getProperties() {
return this.card.isMailList ? "MailList" : "";
},
};

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

@ -696,6 +696,139 @@ AddrBookDirectoryInner.prototype = {
return true;
},
search(query, listener) {
if (!listener) {
return;
}
if (!query) {
listener.onSearchFinished(
Ci.nsIAbDirectoryQueryResultListener.queryResultStopped,
"No query specified."
);
return;
}
if (query[0] == "?") {
query = query.substring(1);
}
let results = Array.from(
this._lists.values(),
list =>
new AddrBookMailingList(
list.uid,
this,
list.localId,
list.name,
list.nickName,
list.description
).asCard
).concat(Array.from(this._cards.values(), card => this._getCard(card)));
// Process the query string into a tree of conditions to match.
let lispRegexp = /^\((and|or|not|([^\)]*)(\)+))/;
let index = 0;
let rootQuery = { children: [], op: "or" };
let currentQuery = rootQuery;
while (true) {
let match = lispRegexp.exec(query.substring(index));
if (!match) {
break;
}
index += match[0].length;
if (["and", "or", "not"].includes(match[1])) {
// For the opening bracket, step down a level.
let child = {
parent: currentQuery,
children: [],
op: match[1],
};
currentQuery.children.push(child);
currentQuery = child;
} else {
currentQuery.children.push(match[2]);
// For each closing bracket except the first, step up a level.
for (let i = match[3].length - 1; i > 0; i--) {
currentQuery = currentQuery.parent;
}
}
}
results = results.filter(card => {
let properties;
if (card.isMailList) {
properties = new Map([
["DisplayName", card.displayName],
["NickName", card.getProperty("NickName")],
["Notes", card.getProperty("Notes")],
]);
} else {
properties = this._loadCardProperties(card.UID);
}
let matches = b => {
if (typeof b == "string") {
let [name, condition, value] = b.split(",");
if (name == "IsMailList" && condition == "=") {
return card.isMailList == (value == "TRUE");
}
if (!properties.has(name)) {
return condition == "!ex";
}
if (condition == "ex") {
return true;
}
value = decodeURIComponent(value).toLowerCase();
let cardValue = properties.get(name).toLowerCase();
switch (condition) {
case "=":
return cardValue == value;
case "!=":
return cardValue != value;
case "lt":
return cardValue < value;
case "gt":
return cardValue > value;
case "bw":
return cardValue.startsWith(value);
case "ew":
return cardValue.endsWith(value);
case "c":
return cardValue.includes(value);
case "!c":
return !cardValue.includes(value);
case "~=":
case "regex":
default:
return false;
}
}
if (b.op == "or") {
return b.children.some(bb => matches(bb));
}
if (b.op == "and") {
return b.children.every(bb => matches(bb));
}
if (b.op == "not") {
return !matches(b.children[0]);
}
return false;
};
return matches(rootQuery);
}, this);
for (let card of results) {
listener.onSearchFoundCard(card);
}
listener.onSearchFinished(
Ci.nsIAbDirectoryQueryResultListener.queryResultComplete,
""
);
},
generateName(generateFormat, bundle) {
return this.dirName;
},

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

@ -6,6 +6,7 @@
#include "nsISupports.idl"
interface nsIAbCard;
interface nsIAbDirSearchListener;
interface nsIArray;
interface nsIMutableArray;
interface nsISimpleEnumerator;
@ -193,6 +194,23 @@ interface nsIAbDirectory : nsISupports {
*/
readonly attribute nsISimpleEnumerator childCards;
/**
* Searches the directory for cards matching query.
*
* The query takes the form:
* (BOOL1(FIELD1,OP1,VALUE1)..(FIELDn,OPn,VALUEn)(BOOL2(FIELD1,OP1,VALUE1)...)...)
*
* BOOLn A boolean operator joining subsequent terms delimited by ().
* For possible values see CreateBooleanExpression().
* FIELDn An addressbook card data field.
* OPn An operator for the search term.
* For possible values see CreateBooleanConditionString().
* VALUEn The value to be matched in the FIELDn via the OPn operator.
* The value must be URL encoded by the caller, if it contains any
* special characters including '(' and ')'.
*/
void search(in AString query, in nsIAbDirSearchListener listener);
/**
* Returns true if this directory represents a query - i.e. the rdf resource
* was something like moz-abmdbdirectory://abook.mab?....

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

@ -198,53 +198,48 @@ AbAutoCompleteSearch.prototype = {
* @param result The result element to append results to.
*/
_searchCards(searchQuery, directory, result) {
let childCards;
try {
childCards = this._abManager.getDirectory(directory.URI + searchQuery)
.childCards;
} catch (e) {
Cu.reportError(
"Error running addressbook query '" + searchQuery + "': " + e
);
return;
}
// Cache this values to save going through xpconnect each time
var commentColumn = this._commentColumn == 1 ? directory.dirName : "";
let commentColumn = this._commentColumn == 1 ? directory.dirName : "";
// Now iterate through all the cards.
while (childCards.hasMoreElements()) {
var card = childCards.getNext();
if (card instanceof Ci.nsIAbCard) {
if (card.isMailList) {
this._addToResult(commentColumn, directory, card, "", true, result);
} else {
let email = card.primaryEmail;
if (email) {
this._addToResult(
commentColumn,
directory,
card,
email,
true,
result
);
}
email = card.getProperty("SecondEmail", "");
if (email) {
this._addToResult(
commentColumn,
directory,
card,
email,
false,
result
);
}
}
}
if (searchQuery[0] == "?") {
searchQuery = searchQuery.substring(1);
}
return new Promise(resolve => {
this._abManager.getDirectory(directory.URI).search(searchQuery, {
onSearchFoundCard: card => {
if (card.isMailList) {
this._addToResult(commentColumn, directory, card, "", true, result);
} else {
let email = card.primaryEmail;
if (email) {
this._addToResult(
commentColumn,
directory,
card,
email,
true,
result
);
}
email = card.getProperty("SecondEmail", "");
if (email) {
this._addToResult(
commentColumn,
directory,
card,
email,
false,
result
);
}
}
},
onSearchFinished(result, errorMsg) {
resolve();
},
});
});
},
/**
@ -379,7 +374,7 @@ AbAutoCompleteSearch.prototype = {
* It is expected that aSearchParam contains the identity (if any) to use
* for determining if an address book should be autocompleted against.
*/
startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) {
async startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) {
let params = aSearchParam ? JSON.parse(aSearchParam) : {};
var result = new nsAbAutoCompleteResult(aSearchString);
if ("type" in params && !this.applicableHeaders.has(params.type)) {
@ -470,7 +465,7 @@ AbAutoCompleteSearch.prototype = {
// just going to find duplicates.
for (let dir of this._abManager.directories) {
if (dir.useForAutocomplete("idKey" in params ? params.idKey : null)) {
this._searchCards(searchQuery, dir, result);
await this._searchCards(searchQuery, dir, result);
}
}

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

@ -562,3 +562,8 @@ NS_IMETHODIMP nsAbDirProperty::SetLocalizedStringValue(
return m_DirectoryPrefs->SetComplexValue(
aName, NS_GET_IID(nsIPrefLocalizedString), locStr);
}
NS_IMETHODIMP nsAbDirProperty::Search(const nsAString &query,
nsIAbDirSearchListener *listener) {
return NS_ERROR_NOT_IMPLEMENTED;
}

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

@ -134,10 +134,6 @@ NS_IMETHODIMP nsAbLDAPDirectory::GetChildCards(nsISimpleEnumerator **result) {
rv = directory->GetChildCards(result);
} else {
// Start the search
rv = StartSearch();
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewEmptyEnumerator(result);
}
@ -248,10 +244,42 @@ NS_IMETHODIMP nsAbLDAPDirectory::SetLDAPURL(nsILDAPURL *aUrl) {
return NS_OK;
}
nsresult nsAbLDAPDirectory::StartSearch() {
if (!mIsQueryURI || mQueryString.IsEmpty()) return NS_OK;
NS_IMETHODIMP nsAbLDAPDirectory::Search(const nsAString &query,
nsIAbDirSearchListener *listener) {
// When offline, get the child cards from the local, replicated directory.
bool offline;
nsCOMPtr<nsIIOService> ioService = mozilla::services::GetIOService();
NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
nsresult rv = ioService->GetOffline(&offline);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = Initiate();
if (offline) {
nsCString fileName;
rv = GetReplicationFileName(fileName);
NS_ENSURE_SUCCESS(rv, rv);
// If there is no fileName, bail out now.
if (fileName.IsEmpty()) {
listener->OnSearchFinished(1, EmptyString());
return NS_OK;
}
// Get the local directory.
nsAutoCString localDirectoryURI(NS_LITERAL_CSTRING(kJSDirectoryRoot));
localDirectoryURI.Append(fileName);
nsCOMPtr<nsIAbDirectory> directory =
do_CreateInstance(NS_ABJSDIRECTORY_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = directory->Init(localDirectoryURI.get());
NS_ENSURE_SUCCESS(rv, rv);
// Perform the query.
return directory->Search(query, listener);
}
rv = Initiate();
NS_ENSURE_SUCCESS(rv, rv);
rv = StopSearch();
@ -262,7 +290,7 @@ nsresult nsAbLDAPDirectory::StartSearch() {
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIAbBooleanExpression> expression;
rv = nsAbQueryStringToExpression::Convert(mQueryString,
rv = nsAbQueryStringToExpression::Convert(NS_ConvertUTF16toUTF8(query),
getter_AddRefs(expression));
NS_ENSURE_SUCCESS(rv, rv);
@ -272,13 +300,13 @@ nsresult nsAbLDAPDirectory::StartSearch() {
rv = arguments->SetQuerySubDirectories(true);
NS_ENSURE_SUCCESS(rv, rv);
// Get the max hits to return
// Get the max hits to return.
int32_t maxHits;
rv = GetMaxHits(&maxHits);
if (NS_FAILED(rv)) maxHits = kDefaultMaxHits;
// get the appropriate ldap attribute map, and pass it in via the
// TypeSpecificArgument
// Get the appropriate ldap attribute map, and pass it in via the
// TypeSpecificArgument.
nsCOMPtr<nsIAbLDAPAttributeMap> attrMap;
rv = GetAttributeMap(getter_AddRefs(attrMap));
NS_ENSURE_SUCCESS(rv, rv);
@ -286,22 +314,12 @@ nsresult nsAbLDAPDirectory::StartSearch() {
rv = arguments->SetTypeSpecificArg(attrMap);
NS_ENSURE_SUCCESS(rv, rv);
if (!mDirectoryQuery) {
mDirectoryQuery =
do_CreateInstance(NS_ABLDAPDIRECTORYQUERY_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// Perform the query
rv = mDirectoryQuery->DoQuery(this, arguments, this, maxHits, 0, &mContext);
mDirectoryQuery = do_CreateInstance(NS_ABLDAPDIRECTORYQUERY_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Enter lock
MutexAutoLock lock(mLock);
mPerformingQuery = true;
mCache.Clear();
return rv;
// Perform the query.
return mDirectoryQuery->DoQuery(this, arguments, listener, maxHits, 0,
&mContext);
}
nsresult nsAbLDAPDirectory::StopSearch() {

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

@ -33,6 +33,8 @@ class nsAbLDAPDirectory : public nsAbDirProperty, // nsIAbDirectory
NS_IMETHOD GetChildNodes(nsISimpleEnumerator **result) override;
NS_IMETHOD GetChildCards(nsISimpleEnumerator **result) override;
NS_IMETHOD GetIsQuery(bool *aResult) override;
NS_IMETHOD Search(const nsAString &query,
nsIAbDirSearchListener *listener) override;
NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override;
NS_IMETHOD GetSupportsMailingLists(bool *aSupportsMailingsLists) override;
NS_IMETHOD GetReadOnly(bool *aReadOnly) override;
@ -51,7 +53,6 @@ class nsAbLDAPDirectory : public nsAbDirProperty, // nsIAbDirectory
virtual ~nsAbLDAPDirectory();
nsresult Initiate();
nsresult StartSearch();
nsresult StopSearch();
bool mPerformingQuery;

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

@ -79,6 +79,8 @@ class nsAbOSXDirectory final : public nsAbDirProperty,
nsISimpleEnumerator **aResult) override;
NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress,
nsIAbCard **aResult) override;
NS_IMETHOD Search(const nsAString &query,
nsIAbDirSearchListener *listener) override;
// nsIAbOSXDirectory
nsresult AssertChildNodes() override;
@ -101,8 +103,6 @@ class nsAbOSXDirectory final : public nsAbDirProperty,
private:
~nsAbOSXDirectory();
nsresult FallbackSearch(nsIAbBooleanExpression *aExpression,
nsISimpleEnumerator **aCards);
// This is a list of nsIAbCards, kept separate from m_AddressList because:
// - nsIAbDirectory items that are mailing lists, must keep a list of

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

@ -216,208 +216,6 @@ static nsresult Sync(NSString *aUid) {
}
@end
static nsresult MapConditionString(nsIAbBooleanConditionString *aCondition, bool aNegate,
bool &aCanHandle, ABSearchElement **aResult) {
aCanHandle = false;
nsAbBooleanConditionType conditionType = 0;
nsresult rv = aCondition->GetCondition(&conditionType);
NS_ENSURE_SUCCESS(rv, rv);
ABSearchComparison comparison;
switch (conditionType) {
case nsIAbBooleanConditionTypes::Contains: {
if (!aNegate) {
comparison = kABContainsSubString;
aCanHandle = true;
}
break;
}
case nsIAbBooleanConditionTypes::DoesNotContain: {
if (aNegate) {
comparison = kABContainsSubString;
aCanHandle = true;
}
break;
}
case nsIAbBooleanConditionTypes::Is: {
comparison = aNegate ? kABNotEqual : kABEqual;
aCanHandle = true;
break;
}
case nsIAbBooleanConditionTypes::IsNot: {
comparison = aNegate ? kABEqual : kABNotEqual;
aCanHandle = true;
break;
}
case nsIAbBooleanConditionTypes::BeginsWith: {
if (!aNegate) {
comparison = kABPrefixMatch;
aCanHandle = true;
}
break;
}
case nsIAbBooleanConditionTypes::EndsWith: {
// comparison = kABSuffixMatch;
break;
}
case nsIAbBooleanConditionTypes::LessThan: {
comparison = aNegate ? kABGreaterThanOrEqual : kABLessThan;
aCanHandle = true;
break;
}
case nsIAbBooleanConditionTypes::GreaterThan: {
comparison = aNegate ? kABLessThanOrEqual : kABGreaterThan;
aCanHandle = true;
break;
}
}
if (!aCanHandle) return NS_OK;
nsCString name;
rv = aCondition->GetName(getter_Copies(name));
NS_ENSURE_SUCCESS(rv, rv);
nsString value;
rv = aCondition->GetValue(getter_Copies(value));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t length = value.Length();
uint32_t i;
for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
if (name.Equals(nsAbOSXUtils::kPropertyMap[i].mPropertyName)) {
*aResult = [ABPerson
searchElementForProperty:nsAbOSXUtils::kPropertyMap[i].mOSXProperty
label:nsAbOSXUtils::kPropertyMap[i].mOSXLabel
key:nsAbOSXUtils::kPropertyMap[i].mOSXKey
value:[NSString stringWithCharacters:reinterpret_cast<const unichar *>(
value.get())
length:length]
comparison:comparison];
return NS_OK;
}
}
if (name.EqualsLiteral("DisplayName") && comparison == kABContainsSubString) {
ABSearchElement *first = [ABPerson
searchElementForProperty:kABFirstNameProperty
label:nil
key:nil
value:[NSString stringWithCharacters:reinterpret_cast<const unichar *>(
value.get())
length:length]
comparison:comparison];
ABSearchElement *second = [ABPerson
searchElementForProperty:kABLastNameProperty
label:nil
key:nil
value:[NSString stringWithCharacters:reinterpret_cast<const unichar *>(
value.get())
length:length]
comparison:comparison];
ABSearchElement *third = [ABGroup
searchElementForProperty:kABGroupNameProperty
label:nil
key:nil
value:[NSString stringWithCharacters:reinterpret_cast<const unichar *>(
value.get())
length:length]
comparison:comparison];
*aResult = [ABSearchElement
searchElementForConjunction:kABSearchOr
children:[NSArray arrayWithObjects:first, second, third, nil]];
return NS_OK;
}
aCanHandle = false;
return NS_OK;
}
static nsresult BuildSearchElements(nsIAbBooleanExpression *aExpression, bool &aCanHandle,
ABSearchElement **aResult) {
aCanHandle = true;
nsCOMPtr<nsIArray> expressions;
nsresult rv = aExpression->GetExpressions(getter_AddRefs(expressions));
NS_ENSURE_SUCCESS(rv, rv);
nsAbBooleanOperationType operation;
rv = aExpression->GetOperation(&operation);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t count;
rv = expressions->GetLength(&count);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(count > 1 && operation != nsIAbBooleanOperationTypes::NOT,
"This doesn't make sense!");
NSMutableArray *array = nullptr;
if (count > 1) array = [[NSMutableArray alloc] init];
uint32_t i;
nsCOMPtr<nsIAbBooleanConditionString> condition;
nsCOMPtr<nsIAbBooleanExpression> subExpression;
for (i = 0; i < count; ++i) {
ABSearchElement *element = nullptr;
condition = do_QueryElementAt(expressions, i);
if (condition) {
rv = MapConditionString(condition, operation == nsIAbBooleanOperationTypes::NOT, aCanHandle,
&element);
if (NS_FAILED(rv)) break;
} else {
subExpression = do_QueryElementAt(expressions, i);
if (subExpression) {
rv = BuildSearchElements(subExpression, aCanHandle, &element);
if (NS_FAILED(rv)) break;
}
}
if (!aCanHandle) {
// remember to free the array when returning early
[array release];
return NS_OK;
}
if (element) {
if (array)
[array addObject:element];
else
*aResult = element;
}
}
if (array) {
if (NS_SUCCEEDED(rv)) {
ABSearchConjunction conjunction =
operation == nsIAbBooleanOperationTypes::AND ? kABSearchAnd : kABSearchOr;
*aResult = [ABSearchElement searchElementForConjunction:conjunction children:array];
}
[array release];
}
return rv;
}
static bool Search(nsIAbBooleanExpression *aExpression, NSArray **aResult) {
bool canHandle = false;
ABSearchElement *searchElement;
nsresult rv = BuildSearchElements(aExpression, canHandle, &searchElement);
NS_ENSURE_SUCCESS(rv, false);
if (canHandle)
*aResult = [[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:searchElement];
return canHandle;
}
static uint32_t sObserverCount = 0;
static ABChangedMonitor *sObserver = nullptr;
@ -837,51 +635,8 @@ nsAbOSXDirectory::GetChildCards(nsISimpleEnumerator **aCards) {
NS_ENSURE_ARG_POINTER(aCards);
nsresult rv;
NSArray *cards;
if (mIsQueryURI) {
nsCOMPtr<nsIAbBooleanExpression> expression;
rv = nsAbQueryStringToExpression::Convert(mQueryString, getter_AddRefs(expression));
NS_ENSURE_SUCCESS(rv, rv);
bool canHandle = !m_IsMailList && Search(expression, &cards);
if (!canHandle) return FallbackSearch(expression, aCards);
if (!mCardList)
mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
else
mCardList->Clear();
NS_ENSURE_SUCCESS(rv, rv);
// The uuid for initializing cards
nsAutoCString ourUuid;
GetUuid(ourUuid);
// Fill the results array and update the card list
unsigned int nbCards = [cards count];
unsigned int i;
nsCOMPtr<nsIAbCard> card;
nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
NS_ENSURE_SUCCESS(rv, rv);
for (i = 0; i < nbCards; ++i) {
rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card), rootOSXDirectory);
if (NS_FAILED(rv)) rv = CreateCard([cards objectAtIndex:i], getter_AddRefs(card));
NS_ENSURE_SUCCESS(rv, rv);
card->SetDirectoryId(ourUuid);
mCardList->AppendElement(card);
}
return NS_NewArrayEnumerator(aCards, mCardList, NS_GET_IID(nsIAbCard));
}
// Not a search, so just return the appropriate list of items.
return m_IsMailList ? NS_NewArrayEnumerator(aCards, m_AddressList, NS_GET_IID(nsIAbDirectory))
return m_IsMailList ? NS_NewArrayEnumerator(aCards, m_AddressList, NS_GET_IID(nsIAbCard))
: NS_NewArrayEnumerator(aCards, mCardList, NS_GET_IID(nsIAbCard));
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
@ -1092,28 +847,20 @@ nsAbOSXDirectory::OnSearchFoundCard(nsIAbCard *aCard) {
return NS_OK;
}
nsresult nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression,
nsISimpleEnumerator **aCards) {
NS_IMETHODIMP
nsAbOSXDirectory::Search(const nsAString &query, nsIAbDirSearchListener *listener) {
nsresult rv;
if (mCardList)
rv = mCardList->Clear();
else
mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
nsCOMPtr<nsIAbBooleanExpression> expression;
rv = nsAbQueryStringToExpression::Convert(NS_ConvertUTF16toUTF8(query),
getter_AddRefs(expression));
NS_ENSURE_SUCCESS(rv, rv);
if (m_AddressList) {
m_AddressList->Clear();
} else {
m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIAbDirectoryQueryArguments> arguments =
do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = arguments->SetExpression(aExpression);
rv = arguments->SetExpression(expression);
NS_ENSURE_SUCCESS(rv, rv);
// Don't search the subdirectories. If the current directory is a mailing
@ -1124,14 +871,6 @@ nsresult nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression,
rv = arguments->SetQuerySubDirectories(false);
NS_ENSURE_SUCCESS(rv, rv);
// Get the directory without the query
nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIAbDirectory> directory;
rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory));
NS_ENSURE_SUCCESS(rv, rv);
// Initiate the proxy query with the no query directory
nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv);
@ -1141,10 +880,10 @@ nsresult nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression,
NS_ENSURE_SUCCESS(rv, rv);
int32_t context = 0;
rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &context);
rv = queryProxy->DoQuery(this, arguments, listener, -1, 0, &context);
NS_ENSURE_SUCCESS(rv, rv);
return NS_NewArrayEnumerator(aCards, m_AddressList, NS_GET_IID(nsIAbDirectory));
return NS_OK;
}
nsresult nsAbOSXDirectory::DeleteUid(const nsACString &aUid) {

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

@ -31,3 +31,22 @@ function promiseDirectoryRemoved() {
);
});
}
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
_resolve: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
this._resolve();
},
waitForResult() {
return new Promise(resolve => {
this._resolve = resolve;
});
},
};

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

@ -101,18 +101,6 @@ var inputs = [
bothNames,
];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
var PAB_CARD_DATA = [
{
FirstName: "firs",
@ -238,7 +226,7 @@ function setupAddressBookData(aDirURI, aCardData, aMailListData) {
});
}
function run_test() {
add_task(async () => {
// Set up addresses for in the personal address book.
setupAddressBookData(kPABData.URI, PAB_CARD_DATA, PAB_LIST_DATA);
// ... and collected addresses address book.
@ -262,7 +250,9 @@ function run_test() {
let paramNews = JSON.stringify({ type: "addr_newsgroups" });
let paramFollowup = JSON.stringify({ type: "addr_followup" });
let resultPromise = obs.waitForResult();
acs.startSearch("abc", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "abc");
@ -274,7 +264,9 @@ function run_test() {
Services.prefs.setBoolPref("mail.enable_autocomplete", true);
resultPromise = obs.waitForResult();
acs.startSearch(null, param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, null);
@ -285,7 +277,9 @@ function run_test() {
// Test - Check ignoring result with comma
resultPromise = obs.waitForResult();
acs.startSearch("a,b", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "a,b");
@ -296,7 +290,9 @@ function run_test() {
// Test - No matches
resultPromise = obs.waitForResult();
acs.startSearch("asjdkljdgfjglkfg", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "asjdkljdgfjglkfg");
@ -308,7 +304,9 @@ function run_test() {
// Test - Matches
// Basic quick-check
resultPromise = obs.waitForResult();
acs.startSearch("email", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "email");
@ -324,17 +322,23 @@ function run_test() {
Assert.equal(obs._result.getImageAt(0), "");
// quick-check that nothing is found for addr_newsgroups
resultPromise = obsNews.waitForResult();
acs.startSearch("email", paramNews, null, obsNews);
await resultPromise;
Assert.ok(obsNews._result == null || obsNews._result.matchCount == 0);
// quick-check that nothing is found for addr_followup
resultPromise = obsFollowup.waitForResult();
acs.startSearch("a@b", paramFollowup, null, obsFollowup);
await resultPromise;
Assert.ok(obsFollowup._result == null || obsFollowup._result.matchCount == 0);
// Now quick-check with the address book name in the comment column.
Services.prefs.setIntPref("mail.autoComplete.commentColumn", 1);
resultPromise = obs.waitForResult();
acs.startSearch("email", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "email");
@ -350,7 +354,9 @@ function run_test() {
Assert.equal(obs._result.getImageAt(0), "");
// Check input with different case
resultPromise = obs.waitForResult();
acs.startSearch("EMAIL", param, null, obs);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, "EMAIL");
@ -366,10 +372,12 @@ function run_test() {
Assert.equal(obs._result.getImageAt(0), "");
// Now check multiple matches
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
let prevRes = obs._result;
print("Search #" + index + ": search=" + element.search);
resultPromise = obs.waitForResult();
acs.startSearch(element.search, param, prevRes, obs);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -410,11 +418,12 @@ function run_test() {
Assert.equal(obs._result.getImageAt(i), "");
}
}
function checkInputSet(element, index, array) {
element.forEach(checkInputItem);
}
inputs.forEach(checkInputSet);
for (let inputSet of inputs) {
for (let i = 0; i < inputSet.length; i++) {
await checkInputItem(inputSet[i], i);
}
}
// Test - Popularity Index
print("Checking by popularity index:");
@ -455,5 +464,7 @@ function run_test() {
{ search: "displa", expected: [5] },
];
popularitySearch.forEach(checkInputItem);
}
for (let i = 0; i < popularitySearch.length; i++) {
await checkInputItem(popularitySearch[i], i);
}
});

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

@ -113,19 +113,7 @@ var lastNames = [
var inputs = [firstNames, lastNames];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
function run_test() {
add_task(async () => {
// Test - Create a new search component
var acs = Cc["@mozilla.org/autocomplete/search;1?name=addrbook"].getService(
@ -155,13 +143,15 @@ function run_test() {
// Test - Matches
// Now check multiple matches
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
let resultPromise = obs.waitForResult();
acs.startSearch(
element.search,
JSON.stringify({ type: "addr_to", idKey: "" }),
lastResult,
obs
);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, element.search);
@ -186,9 +176,10 @@ function run_test() {
Assert.equal(obs._result.getImageAt(i), "");
}
}
function checkInputSet(element, index, array) {
element.forEach(checkInputItem);
}
inputs.forEach(checkInputSet);
}
for (let inputSet of inputs) {
for (let i = 0; i < inputSet.length; i++) {
await checkInputItem(inputSet[i], i);
}
}
});

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

@ -80,19 +80,7 @@ var duplicates = [
{ search: "(bracket)", expected: [7, 8] },
];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
function run_test() {
add_task(async () => {
// We set up the cards for this test manually as it is easier to set the
// popularity index and we don't need many.
@ -124,14 +112,16 @@ function run_test() {
var obs = new acObserver();
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
print("Search #" + index + ": search=" + element.search);
let resultPromise = obs.waitForResult();
acs.startSearch(
element.search,
JSON.stringify({ type: "addr_to" }),
null,
obs
);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -168,5 +158,7 @@ function run_test() {
}
}
duplicates.forEach(checkInputItem);
}
for (let i = 0; i < duplicates.length; i++) {
await checkInputItem(duplicates[i], i);
}
});

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

@ -140,19 +140,7 @@ var reductionExpectedResults = [
["boo2@test.invalid"],
];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
function run_test() {
add_task(async () => {
// We set up the cards for this test manually as it is easier to set the
// popularity index and we don't need many.
@ -189,14 +177,16 @@ function run_test() {
print("Checking Initial Searches");
function checkSearch(element, index, array) {
async function checkSearch(element, index) {
print("Search #" + index + ": search=" + element);
let resultPromise = obs.waitForResult();
acs.startSearch(
element,
JSON.stringify({ type: "addr_to", idKey: "" }),
null,
obs
);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -218,19 +208,23 @@ function run_test() {
}
}
searches.forEach(checkSearch);
for (let i = 0; i < searches.length; i++) {
await checkSearch(searches[i], i);
}
print("Checking Reduction of Search Results");
var lastResult = null;
function checkReductionSearch(element, index, array) {
async function checkReductionSearch(element, index) {
let resultPromise = obs.waitForResult();
acs.startSearch(
element,
JSON.stringify({ type: "addr_to", idKey: "" }),
lastResult,
obs
);
await resultPromise;
Assert.equal(obs._search, acs);
Assert.equal(obs._result.searchString, element);
@ -257,5 +251,8 @@ function run_test() {
}
lastResult = obs._result;
}
reductionSearches.forEach(checkReductionSearch);
}
for (let i = 0; i < reductionSearches.length; i++) {
await checkReductionSearch(reductionSearches[i], i);
}
});

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

@ -37,19 +37,7 @@ var lastNames = [
var inputs = [firstNames, lastNames];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
function run_test() {
add_task(async () => {
loadABFile("../../../data/tb2hexpopularity", kPABData.fileName);
// Test - Create a new search component
@ -66,14 +54,16 @@ function run_test() {
// Test - Matches
// Now check multiple matches
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
print("Search #" + index + ": search=" + element.search);
let resultPromise = obs.waitForResult();
acs.startSearch(
element.search,
JSON.stringify({ type: "addr_to" }),
null,
obs
);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -121,9 +111,10 @@ function run_test() {
}
}
}
function checkInputSet(element, index, array) {
element.forEach(checkInputItem);
}
inputs.forEach(checkInputSet);
}
for (let inputSet of inputs) {
for (let i = 0; i < inputSet.length; i++) {
await checkInputItem(inputSet[i], i);
}
}
});

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

@ -168,19 +168,7 @@ var inputs = [
{ search: "short", expected: [14] },
];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
function run_test() {
add_task(async () => {
// We set up the cards for this test manually as it is easier to set the
// popularity index and we don't need many.
@ -216,14 +204,16 @@ function run_test() {
var obs = new acObserver();
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
print("Search #" + index + ": search=" + element.search);
let resultPromise = obs.waitForResult();
acs.startSearch(
element.search,
JSON.stringify({ type: "addr_to" }),
null,
obs
);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -252,5 +242,7 @@ function run_test() {
}
}
inputs.forEach(checkInputItem);
}
for (let i = 0; i < inputs.length; i++) {
await checkInputItem(inputs[i], i);
}
});

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

@ -27,18 +27,6 @@ var inputs = [
],
];
function acObserver() {}
acObserver.prototype = {
_search: null,
_result: null,
onSearchResult(aSearch, aResult) {
this._search = aSearch;
this._result = aResult;
},
};
var PAB_CARD_DATA = [
{
FirstName: "Tomas",
@ -102,7 +90,7 @@ function setupAddressBookData(aDirURI, aCardData, aMailListData) {
});
}
function run_test() {
add_task(async () => {
// Set up addresses for in the personal address book.
setupAddressBookData(kPABData.URI, PAB_CARD_DATA, []);
@ -117,10 +105,12 @@ function run_test() {
let param = JSON.stringify({ type: "addr_to" });
// Now check multiple matches
function checkInputItem(element, index, array) {
async function checkInputItem(element, index) {
let prevRes = obs._result;
print("Search #" + index + ": search=" + element.search);
let resultPromise = obs.waitForResult();
acs.startSearch(element.search, param, prevRes, obs);
await resultPromise;
for (let i = 0; i < obs._result.matchCount; i++) {
print("... got " + i + ": " + obs._result.getValueAt(i));
@ -157,9 +147,10 @@ function run_test() {
Assert.equal(obs._result.getImageAt(i), "");
}
}
function checkInputSet(element, index, array) {
element.forEach(checkInputItem);
}
inputs.forEach(checkInputSet);
}
for (let inputSet of inputs) {
for (let i = 0; i < inputSet.length; i++) {
await checkInputItem(inputSet[i], i);
}
}
});

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

@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { getModelQuery, generateQueryURI } = ChromeUtils.import(
"resource:///modules/ABQueryUtils.jsm"
);
const jsonFile = do_get_file("data/ldap_contacts.json");
add_task(async () => {
let contents = await OS.File.read(jsonFile.path);
let contacts = await JSON.parse(new TextDecoder().decode(contents));
let dirPrefId = MailServices.ab.newAddressBook("new book", "", 101);
let book = MailServices.ab.getDirectoryFromId(dirPrefId);
for (let [name, { attributes }] of Object.entries(contacts)) {
let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
Ci.nsIAbCard
);
card.displayName = attributes.cn;
card.firstName = attributes.givenName;
card.lastName = attributes.sn;
card.primaryEmail = attributes.mail;
contacts[name] = book.addCard(card);
}
let doSearch = async function(searchString, ...expectedContacts) {
let foundCards = await new Promise(resolve => {
let listener = {
cards: [],
onSearchFoundCard(card) {
this.cards.push(card);
},
onSearchFinished(result, errorMessage) {
resolve(this.cards);
},
};
book.search(searchString, listener);
});
Assert.equal(foundCards.length, expectedContacts.length);
for (let name of expectedContacts) {
Assert.ok(foundCards.find(c => c.equals(contacts[name])));
}
};
await doSearch("(DisplayName,c,watson)", "john", "mary");
let modelQuery = getModelQuery("mail.addr_book.autocompletequery.format");
await doSearch(
generateQueryURI(modelQuery, ["holmes"]),
"eurus",
"mycroft",
"sherlock"
);
await doSearch(generateQueryURI(modelQuery, ["adler"]), "irene");
await doSearch(generateQueryURI(modelQuery, ["redbeard"]));
});

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

@ -38,3 +38,4 @@ skip-if = debug # Fails for unknown reasons.
[test_nsAbManager4.js]
[test_nsAbManager5.js]
[test_nsIAbCard.js]
[test_search.js]

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

@ -12,6 +12,7 @@ messenger.jar:
content/messenger/addressbook/abResultsPane.js (addrbook/content/abResultsPane.js)
content/messenger/addressbook/abDragDrop.js (addrbook/content/abDragDrop.js)
content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js)
content/messenger/addressbook/abView.js (addrbook/content/abView.js)
content/messenger/addressbook/map-list.js (addrbook/content/map-list.js)
content/messagebody/addressbook/print.css (addrbook/content/print.css)
* content/messenger/AccountManager.xhtml (base/prefs/content/AccountManager.xhtml)