Bug 1660129 - Add UI for changing CardDAV directory properties. r=mkmelin

I've reused some of the strings from the AB dialog, rather than making new ones, because I expect
at some stage we'll either migrate all of the AB strings to Fluent or throw them away and start again.

Differential Revision: https://phabricator.services.mozilla.com/D87832

--HG--
rename : mailnews/addrbook/content/abCardDAVDialog.css => mail/themes/shared/mail/cardDAV.css
extra : moz-landing-system : lando
This commit is contained in:
Geoff Lankow 2020-09-11 03:37:33 +00:00
Родитель 456da51358
Коммит d5a474d791
11 изменённых файлов: 548 добавлений и 48 удалений

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

@ -10,6 +10,7 @@ subsuite = thunderbird
tags = addrbook tags = addrbook
[browser_cardDAV_init.js] [browser_cardDAV_init.js]
[browser_cardDAV_properties.js]
[browser_cardDAV_sync.js] [browser_cardDAV_sync.js]
[browser_contact_tree.js] [browser_contact_tree.js]
[browser_directory_tree.js] [browser_directory_tree.js]

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

@ -0,0 +1,246 @@
/* 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/. */
/**
* Tests CardDAV properties dialog.
*/
const { CardDAVDirectory } = ChromeUtils.import(
"resource:///modules/CardDAVDirectory.jsm"
);
const { CardDAVServer } = ChromeUtils.import(
"resource://testing-common/CardDAVServer.jsm"
);
add_task(async () => {
const INTERVAL_PREF = "ldap_2.servers.props.carddav.syncinterval";
const TOKEN_PREF = "ldap_2.servers.props.carddav.token";
const TOKEN_VALUE = "http://mochi.test/sync/0";
const URL_PREF = "ldap_2.servers.props.carddav.url";
const URL_VALUE = "https://mochi.test/carddav/test";
let dirPrefId = MailServices.ab.newAddressBook(
"props",
undefined,
Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE
);
Assert.equal(dirPrefId, "ldap_2.servers.props");
Assert.equal([...MailServices.ab.directories].length, 3);
let directory = MailServices.ab.getDirectoryFromId(dirPrefId);
let davDirectory = CardDAVDirectory.forFile(directory.fileName);
registerCleanupFunction(async () => {
let removePromise = promiseDirectoryRemoved();
MailServices.ab.deleteAddressBook(directory.URI);
await removePromise;
Assert.equal(davDirectory._syncTimer, null, "sync timer cleaned up");
});
Assert.equal(directory.dirType, Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
Services.prefs.setIntPref(INTERVAL_PREF, 0);
Services.prefs.setStringPref(TOKEN_PREF, TOKEN_VALUE);
Services.prefs.setStringPref(URL_PREF, URL_VALUE);
Assert.ok(davDirectory);
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory._syncToken, TOKEN_VALUE);
Assert.equal(davDirectory._syncTimer, null, "no sync scheduled");
let abWindow = await openAddressBookWindow();
let abDocument = abWindow.document;
registerCleanupFunction(async () => {
await closeAddressBookWindow();
Services.prefs.clearUserPref("mail.addr_book.view.startupURI");
});
// This test becomes unreliable if we don't pause for a moment.
await new Promise(resolve => abWindow.setTimeout(resolve, 500));
openDirectory(directory);
Assert.equal(abWindow.gDirectoryTreeView.rowCount, 4);
Assert.equal(abWindow.gDirectoryTreeView.getIndexForId(directory.URI), 2);
Assert.equal(abWindow.gDirTree.currentIndex, 2);
let menu = abDocument.getElementById("dirTreeContext");
let menuItem = abDocument.getElementById("dirTreeContext-properties");
let subtest = async function(expectedValues, newValues, buttonAction) {
let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
mailTestUtils.treeClick(EventUtils, abWindow, abWindow.gDirTree, 2, 0, {
type: "mousedown",
button: 2,
});
mailTestUtils.treeClick(EventUtils, abWindow, abWindow.gDirTree, 2, 0, {
type: "contextmenu",
});
mailTestUtils.treeClick(EventUtils, abWindow, abWindow.gDirTree, 2, 0, {
type: "mouseup",
button: 2,
});
await shownPromise;
Assert.equal(abWindow.gDirTree.currentIndex, 2);
let dialogPromise = BrowserTestUtils.promiseAlertDialog(
undefined,
"chrome://messenger/content/addressbook/abCardDAVProperties.xhtml",
async dialogWindow => {
let dialogDocument = dialogWindow.document;
let nameInput = dialogDocument.getElementById("carddav-name");
Assert.equal(nameInput.value, expectedValues.name);
if ("name" in newValues) {
nameInput.value = newValues.name;
}
let urlInput = dialogDocument.getElementById("carddav-url");
Assert.equal(urlInput.value, expectedValues.url);
if ("url" in newValues) {
urlInput.value = newValues.url;
}
let refreshActiveInput = dialogDocument.getElementById(
"carddav-refreshActive"
);
let refreshIntervalInput = dialogDocument.getElementById(
"carddav-refreshInterval"
);
Assert.equal(refreshActiveInput.checked, expectedValues.refreshActive);
Assert.equal(
refreshIntervalInput.disabled,
!expectedValues.refreshActive
);
if (
"refreshActive" in newValues &&
newValues.refreshActive != expectedValues.refreshActive
) {
EventUtils.synthesizeMouseAtCenter(
refreshActiveInput,
{},
dialogWindow
);
Assert.equal(refreshIntervalInput.disabled, !newValues.refreshActive);
}
Assert.equal(
refreshIntervalInput.value,
expectedValues.refreshInterval
);
if ("refreshInterval" in newValues) {
refreshIntervalInput.value = newValues.refreshInterval;
}
dialogDocument
.querySelector("dialog")
.getButton(buttonAction)
.click();
}
);
EventUtils.synthesizeMouseAtCenter(menuItem, {}, abWindow);
await dialogPromise;
await new Promise(resolve => abWindow.setTimeout(resolve));
};
info("Open the dialog and cancel it. Nothing should change.");
await subtest(
{
name: "props",
url: URL_VALUE,
refreshActive: false,
refreshInterval: 30,
},
{},
"cancel"
);
Assert.equal(davDirectory.dirName, "props");
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory.getIntValue("carddav.syncinterval", -1), 0);
Assert.equal(davDirectory._syncTimer, null, "no sync scheduled");
info("Open the dialog and accept it. Nothing should change.");
await subtest(
{
name: "props",
url: URL_VALUE,
refreshActive: false,
refreshInterval: 30,
},
{},
"accept"
);
Assert.equal(davDirectory.dirName, "props");
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory.getIntValue("carddav.syncinterval", -1), 0);
Assert.equal(davDirectory._syncTimer, null, "no sync scheduled");
info("Open the dialog and change the values.");
await subtest(
{
name: "props",
url: URL_VALUE,
refreshActive: false,
refreshInterval: 30,
},
{
name: "CardDAV Properties Test",
refreshActive: true,
refreshInterval: 30,
},
"accept"
);
Assert.equal(davDirectory.dirName, "CardDAV Properties Test");
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory.getIntValue("carddav.syncinterval", -1), 30);
Assert.notEqual(davDirectory._syncTimer, null, "sync scheduled");
let currentSyncTimer = davDirectory._syncTimer;
info("Open the dialog and accept it. Nothing should change.");
await subtest(
{
name: "CardDAV Properties Test",
url: URL_VALUE,
refreshActive: true,
refreshInterval: 30,
},
{},
"accept"
);
Assert.equal(davDirectory.dirName, "CardDAV Properties Test");
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory.getIntValue("carddav.syncinterval", -1), 30);
Assert.equal(
davDirectory._syncTimer,
currentSyncTimer,
"same sync scheduled"
);
info("Open the dialog and change the interval.");
await subtest(
{
name: "CardDAV Properties Test",
url: URL_VALUE,
refreshActive: true,
refreshInterval: 30,
},
{ refreshInterval: 60 },
"accept"
);
Assert.equal(davDirectory.dirName, "CardDAV Properties Test");
Assert.equal(davDirectory._serverURL, URL_VALUE);
Assert.equal(davDirectory.getIntValue("carddav.syncinterval", -1), 60);
Assert.greater(
davDirectory._syncTimer,
currentSyncTimer,
"new sync scheduled"
);
});

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

@ -0,0 +1,27 @@
# 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/.
carddav-url-label =
.value = CardDAV URL:
.accesskey = V
carddav-refreshinterval-label =
.label = Synchronize:
.accesskey = S
# Variables:
# $minutes (integer) - Number of minutes between address book synchronizations
carddav-refreshinterval-minutes-value =
.label = { $minutes ->
[one] every minute
*[other] every { $minutes } minutes
}
# Variables:
# $hours (integer) - Number of hours between address book synchronizations
carddav-refreshinterval-hours-value =
.label = { $hours ->
[one] every hour
*[other] every { $hours } hours
}

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

@ -207,6 +207,7 @@
skin/classic/messenger/shared/accountManage.css (../shared/mail/accountManage.css) skin/classic/messenger/shared/accountManage.css (../shared/mail/accountManage.css)
skin/classic/messenger/shared/accountProvisioner.css (../shared/mail/accountProvisioner.css) skin/classic/messenger/shared/accountProvisioner.css (../shared/mail/accountProvisioner.css)
skin/classic/messenger/shared/addressbook.css (../shared/mail/addressbook.css) skin/classic/messenger/shared/addressbook.css (../shared/mail/addressbook.css)
skin/classic/messenger/shared/cardDAV.css (../shared/mail/cardDAV.css)
skin/classic/messenger/shared/cardDialog.css (../shared/mail/cardDialog.css) skin/classic/messenger/shared/cardDialog.css (../shared/mail/cardDialog.css)
skin/classic/messenger/shared/chat.css (../../components/im/themes/chat.css) skin/classic/messenger/shared/chat.css (../../components/im/themes/chat.css)
skin/classic/messenger/shared/compacttheme.css (../shared/mail/compacttheme.css) skin/classic/messenger/shared/compacttheme.css (../shared/mail/compacttheme.css)

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

@ -20,22 +20,6 @@
width: 30em; width: 30em;
} }
th {
font-weight: normal;
text-align: start;
width: 10%;
}
.input-container {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.input-container > * {
flex: 1;
}
#carddav-provider { #carddav-provider {
height: 1.8em; height: 1.8em;
} }
@ -85,3 +69,33 @@ th {
background-size: 16px 16px; background-size: 16px 16px;
} }
} }
#carddav-properties-table {
display: grid;
grid-template-columns: min-content auto;
align-items: baseline;
}
.input-container {
display: flex;
}
.input-container > * {
flex: 1;
}
#carddav-refreshActive-cell {
/* This shouldn't be necessary, but there's no good combination of checkbox
* and label that play nicely with the align-items: baseline above. */
align-self: center;
}
#carddav-refreshInterval-cell {
display: flex;
align-items: baseline;
}
#carddav-refreshInterval {
flex: 1;
margin: 2px 4px;
}

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

@ -64,7 +64,10 @@ function abNameOKButton(event) {
let newDirName = gNameInput.value.trim(); let newDirName = gNameInput.value.trim();
// Do not allow an already existing name. // Do not allow an already existing name.
if (MailServices.ab.directoryNameExists(newDirName)) { if (
MailServices.ab.directoryNameExists(newDirName) &&
(!gDirectory || newDirName != gDirectory.dirName)
) {
const kAlertTitle = document const kAlertTitle = document
.getElementById("bundle_addressBook") .getElementById("bundle_addressBook")
.getString("duplicateNameTitle"); .getString("duplicateNameTitle");

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

@ -4,8 +4,8 @@
- file, you can obtain one at http://mozilla.org/MPL/2.0/. --> - file, you can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?> <?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?> <?xml-stylesheet href="chrome://messenger/skin/shared/cardDAV.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/content/addressbook/abCardDAVDialog.css" type="text/css"?> <?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?> <?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
<window data-l10n-id="carddav-window" <window data-l10n-id="carddav-window"
@ -34,34 +34,29 @@
<description data-l10n-id="carddav-experimental-warning"/> <description data-l10n-id="carddav-experimental-warning"/>
</hbox> </hbox>
<html:table> <html:div id="carddav-properties-table">
<html:tr hidden="hidden"> <!-- <html:div>
<html:th>
<label control="carddav-provider" <label control="carddav-provider"
data-l10n-id="carddav-provider-label" data-l10n-id="carddav-provider-label"
data-l10n-attrs="value,accesskey"/> data-l10n-attrs="value,accesskey"/>
</html:th> </html:div>
<html:td class="input-container"> <html:div class="input-container">
<html:select id="carddav-provider" onchange="handleChangeProvider(event);"> <html:select id="carddav-provider" onchange="handleChangeProvider(event);">
</html:select> </html:select>
</html:td> </html:div> -->
</html:tr> <html:div>
<html:tr>
<html:th>
<label control="carddav-url" <label control="carddav-url"
data-l10n-id="carddav-url-label" data-l10n-id="carddav-url-label"
data-l10n-attrs="value,accesskey"/> data-l10n-attrs="value,accesskey"/>
</html:th> </html:div>
<html:div class="input-container">
<html:td class="input-container">
<html:input id="carddav-url" type="url" <html:input id="carddav-url" type="url"
placeholder="https://carddav.example.com/" placeholder="https://carddav.example.com/"
required="required" required="required"
onblur="handleCardDAVURLBlur(event);" onblur="handleCardDAVURLBlur(event);"
oninput="handleCardDAVURLInput(event);"/> oninput="handleCardDAVURLInput(event);"/>
</html:td> </html:div>
</html:tr> </html:div>
</html:table>
<vbox id="carddav-statusArea" <vbox id="carddav-statusArea"
align="center" align="center"

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

@ -0,0 +1,133 @@
/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { MailServices } = ChromeUtils.import(
"resource:///modules/MailServices.jsm"
);
var gDirectory = window.arguments[0].selectedDirectory;
var gStringBundle,
gNameInput,
gURLInput,
gRefreshActiveInput,
gRefreshMenulist,
gAcceptButton;
window.addEventListener(
"DOMContentLoaded",
() => {
gStringBundle = Services.strings.createBundle(
"chrome://messenger/locale/addressbook/addressBook.properties"
);
document.title = gStringBundle.formatStringFromName(
"addressBookTitleEdit",
[gDirectory.dirName]
);
gNameInput = document.getElementById("carddav-name");
gNameInput.value = gDirectory.dirName;
gNameInput.addEventListener("input", () => {
gAcceptButton.disabled = gNameInput.value.trim() == "";
});
gURLInput = document.getElementById("carddav-url");
gURLInput.value = gDirectory.getStringValue("carddav.url", "");
gRefreshActiveInput = document.getElementById("carddav-refreshActive");
gRefreshActiveInput.addEventListener(
"command",
() => (gRefreshMenulist.disabled = !gRefreshActiveInput.checked)
);
gRefreshMenulist = document.getElementById("carddav-refreshInterval");
initRefreshInterval();
gAcceptButton = document.querySelector("dialog").getButton("accept");
},
{ once: true }
);
window.addEventListener("dialogaccept", event => {
let newDirName = gNameInput.value.trim();
let newSyncInterval = gRefreshActiveInput.checked
? gRefreshMenulist.value
: 0;
if (newDirName != gDirectory.dirName) {
// Do not allow an already existing name.
if (MailServices.ab.directoryNameExists(newDirName)) {
let alertTitle = gStringBundle.GetStringFromName("duplicateNameTitle");
let alertText = gStringBundle.formatStringFromName("duplicateNameText", [
newDirName,
]);
Services.prompt.alert(window, alertTitle, alertText);
event.preventDefault();
return;
}
gDirectory.dirName = newDirName;
}
if (newSyncInterval != gDirectory.getIntValue("carddav.syncinterval", -1)) {
gDirectory.setIntValue("carddav.syncinterval", newSyncInterval);
}
});
function initRefreshInterval() {
function createMenuItem(minutes) {
let menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("value", minutes);
menuitem.setAttribute("data-l10n-attrs", "label");
if (minutes < 60) {
document.l10n.setAttributes(
menuitem,
"carddav-refreshinterval-minutes-value",
{
minutes,
}
);
} else {
document.l10n.setAttributes(
menuitem,
"carddav-refreshinterval-hours-value",
{
hours: minutes / 60,
}
);
}
gRefreshMenulist.menupopup.appendChild(menuitem);
if (refreshInterval == minutes) {
gRefreshMenulist.value = minutes;
foundValue = true;
}
return menuitem;
}
let refreshInterval = gDirectory.getIntValue("carddav.syncinterval", 30);
if (refreshInterval === null) {
refreshInterval = 30;
}
let foundValue = false;
for (let min of [1, 5, 15, 30, 60, 120, 240, 360, 720, 1440]) {
createMenuItem(min);
}
if (refreshInterval == 0) {
gRefreshMenulist.value = 30; // The default.
gRefreshMenulist.disabled = true;
foundValue = true;
} else {
gRefreshActiveInput.checked = true;
}
if (!foundValue) {
// Special menuitem in case the user changed the value in the config editor.
createMenuItem(refreshInterval);
}
}

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

@ -0,0 +1,68 @@
<?xml version="1.0"?>
<!-- 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/. -->
<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/shared/cardDAV.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://messenger/locale/addressbook/abAddressBookNameDialog.dtd">
<window minwidth="500"
lightweightthemes="true"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<dialog id="carddav-properties-dialog">
<linkset>
<html:link rel="localization" href="messenger/addressbook/abCardDAVProperties.ftl"/>
</linkset>
<script src="chrome://global/content/globalOverlay.js"/>
<script src="chrome://global/content/editMenuOverlay.js"/>
<script src="chrome://messenger/content/addressbook/abCardDAVProperties.js"/>
<script src="chrome://messenger/content/dialogShadowDom.js"/>
<div id="carddav-properties-table"
xmlns="http://www.w3.org/1999/xhtml">
<div>
<xul:label control="carddav-name"
value="&name.label;"
accesskey="&name.accesskey;"/>
</div>
<div class="input-container">
<input id="carddav-name"
type="text"
class="input-inline"/>
</div>
<div>
<xul:label data-l10n-id="carddav-url-label"
data-l10n-attrs="value,accesskey"
control="carddav-url"/>
</div>
<div class="input-container">
<input id="carddav-url"
type="url"
class="input-inline"
readonly="readonly"/>
</div>
<div id="carddav-refreshActive-cell">
<xul:checkbox id="carddav-refreshActive"
data-l10n-id="carddav-refreshinterval-label"
data-l10n-attrs="label,accesskey"
control="carddav-refreshInterval"/>
</div>
<div id="carddav-refreshInterval-cell">
<xul:menulist id="carddav-refreshInterval">
<xul:menupopup>
<!-- This will be filled programmatically to reduce the number of needed strings -->
</xul:menupopup>
</xul:menulist>
</div>
</div>
</dialog>
</window>

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

@ -42,6 +42,9 @@ class CardDAVDirectory extends AddrBookDirectory {
} }
} }
get propertiesChromeURI() {
return "chrome://messenger/content/addressbook/abCardDAVProperties.xhtml";
}
get dirType() { get dirType() {
return Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE; return Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE;
} }
@ -114,6 +117,14 @@ class CardDAVDirectory extends AddrBookDirectory {
Cr.NS_ERROR_NOT_IMPLEMENTED Cr.NS_ERROR_NOT_IMPLEMENTED
); );
} }
setIntValue(name, value) {
super.setIntValue(name, value);
// Capture changes to the sync interval from the UI.
if (name == "carddav.syncinterval") {
this._scheduleNextSync();
}
}
/** CardDAV specific */ /** CardDAV specific */
_syncInProgress = false; _syncInProgress = false;

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

@ -9,9 +9,10 @@ messenger.jar:
content/messenger/addressbook/pref-editdirectories.xhtml (addrbook/prefs/content/pref-editdirectories.xhtml) content/messenger/addressbook/pref-editdirectories.xhtml (addrbook/prefs/content/pref-editdirectories.xhtml)
content/messenger/addressbook/abAddressBookNameDialog.js (addrbook/content/abAddressBookNameDialog.js) content/messenger/addressbook/abAddressBookNameDialog.js (addrbook/content/abAddressBookNameDialog.js)
content/messenger/addressbook/abAddressBookNameDialog.xhtml (addrbook/content/abAddressBookNameDialog.xhtml) content/messenger/addressbook/abAddressBookNameDialog.xhtml (addrbook/content/abAddressBookNameDialog.xhtml)
content/messenger/addressbook/abCardDAVDialog.css (addrbook/content/abCardDAVDialog.css)
content/messenger/addressbook/abCardDAVDialog.js (addrbook/content/abCardDAVDialog.js) content/messenger/addressbook/abCardDAVDialog.js (addrbook/content/abCardDAVDialog.js)
content/messenger/addressbook/abCardDAVDialog.xhtml (addrbook/content/abCardDAVDialog.xhtml) content/messenger/addressbook/abCardDAVDialog.xhtml (addrbook/content/abCardDAVDialog.xhtml)
content/messenger/addressbook/abCardDAVProperties.js (addrbook/content/abCardDAVProperties.js)
content/messenger/addressbook/abCardDAVProperties.xhtml (addrbook/content/abCardDAVProperties.xhtml)
content/messenger/addressbook/abResultsPane.js (addrbook/content/abResultsPane.js) content/messenger/addressbook/abResultsPane.js (addrbook/content/abResultsPane.js)
content/messenger/addressbook/abDragDrop.js (addrbook/content/abDragDrop.js) content/messenger/addressbook/abDragDrop.js (addrbook/content/abDragDrop.js)
content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js) content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js)