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:
Родитель
daae6cfd8f
Коммит
09fa8a614f
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче