diff --git a/mail/components/addrbook/test/browser/browser.ini b/mail/components/addrbook/test/browser/browser.ini
index 9ccc5efef7..b01e251236 100644
--- a/mail/components/addrbook/test/browser/browser.ini
+++ b/mail/components/addrbook/test/browser/browser.ini
@@ -10,6 +10,7 @@ subsuite = thunderbird
tags = addrbook
[browser_cardDAV_init.js]
+[browser_cardDAV_properties.js]
[browser_cardDAV_sync.js]
[browser_contact_tree.js]
[browser_directory_tree.js]
diff --git a/mail/components/addrbook/test/browser/browser_cardDAV_properties.js b/mail/components/addrbook/test/browser/browser_cardDAV_properties.js
new file mode 100644
index 0000000000..4a4fe1f00c
--- /dev/null
+++ b/mail/components/addrbook/test/browser/browser_cardDAV_properties.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"
+ );
+});
diff --git a/mail/locales/en-US/messenger/addressbook/abCardDAVProperties.ftl b/mail/locales/en-US/messenger/addressbook/abCardDAVProperties.ftl
new file mode 100644
index 0000000000..9ff9ea2331
--- /dev/null
+++ b/mail/locales/en-US/messenger/addressbook/abCardDAVProperties.ftl
@@ -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
+ }
diff --git a/mail/themes/shared/jar.inc.mn b/mail/themes/shared/jar.inc.mn
index cef7f51cab..473d07506e 100644
--- a/mail/themes/shared/jar.inc.mn
+++ b/mail/themes/shared/jar.inc.mn
@@ -207,6 +207,7 @@
skin/classic/messenger/shared/accountManage.css (../shared/mail/accountManage.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/cardDAV.css (../shared/mail/cardDAV.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/compacttheme.css (../shared/mail/compacttheme.css)
diff --git a/mailnews/addrbook/content/abCardDAVDialog.css b/mail/themes/shared/mail/cardDAV.css
similarity index 78%
rename from mailnews/addrbook/content/abCardDAVDialog.css
rename to mail/themes/shared/mail/cardDAV.css
index 1d865bb8b7..c3b98ac74f 100644
--- a/mailnews/addrbook/content/abCardDAVDialog.css
+++ b/mail/themes/shared/mail/cardDAV.css
@@ -20,22 +20,6 @@
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 {
height: 1.8em;
}
@@ -85,3 +69,33 @@ th {
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;
+}
diff --git a/mailnews/addrbook/content/abAddressBookNameDialog.js b/mailnews/addrbook/content/abAddressBookNameDialog.js
index a2df4129fc..daa8ebba29 100644
--- a/mailnews/addrbook/content/abAddressBookNameDialog.js
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.js
@@ -64,7 +64,10 @@ function abNameOKButton(event) {
let newDirName = gNameInput.value.trim();
// Do not allow an already existing name.
- if (MailServices.ab.directoryNameExists(newDirName)) {
+ if (
+ MailServices.ab.directoryNameExists(newDirName) &&
+ (!gDirectory || newDirName != gDirectory.dirName)
+ ) {
const kAlertTitle = document
.getElementById("bundle_addressBook")
.getString("duplicateNameTitle");
diff --git a/mailnews/addrbook/content/abCardDAVDialog.xhtml b/mailnews/addrbook/content/abCardDAVDialog.xhtml
index 4117dff9be..538e324882 100644
--- a/mailnews/addrbook/content/abCardDAVDialog.xhtml
+++ b/mailnews/addrbook/content/abCardDAVDialog.xhtml
@@ -4,8 +4,8 @@
- file, you can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
{
+ 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);
+ }
+}
diff --git a/mailnews/addrbook/content/abCardDAVProperties.xhtml b/mailnews/addrbook/content/abCardDAVProperties.xhtml
new file mode 100644
index 0000000000..c06465593c
--- /dev/null
+++ b/mailnews/addrbook/content/abCardDAVProperties.xhtml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mailnews/addrbook/jsaddrbook/CardDAVDirectory.jsm b/mailnews/addrbook/jsaddrbook/CardDAVDirectory.jsm
index 2a9c4e11ee..93a2e07adf 100644
--- a/mailnews/addrbook/jsaddrbook/CardDAVDirectory.jsm
+++ b/mailnews/addrbook/jsaddrbook/CardDAVDirectory.jsm
@@ -42,6 +42,9 @@ class CardDAVDirectory extends AddrBookDirectory {
}
}
+ get propertiesChromeURI() {
+ return "chrome://messenger/content/addressbook/abCardDAVProperties.xhtml";
+ }
get dirType() {
return Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE;
}
@@ -114,6 +117,14 @@ class CardDAVDirectory extends AddrBookDirectory {
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 */
_syncInProgress = false;
diff --git a/mailnews/jar.mn b/mailnews/jar.mn
index efc8b85470..b579853b6f 100644
--- a/mailnews/jar.mn
+++ b/mailnews/jar.mn
@@ -9,9 +9,10 @@ messenger.jar:
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.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.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/abDragDrop.js (addrbook/content/abDragDrop.js)
content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js)