Commit 5387f26f authored by Carsten Schoenert's avatar Carsten Schoenert
Browse files

Update upstream source from tag 'upstream/78.7.1'

Update to upstream version '78.7.1'
with Debian dir c3d6c96c23756211503054c3d8d5cabc4501062f
parents 4d7aa109 406f9d79
......@@ -104,7 +104,7 @@ pref("datareporting.policy.firstRunURL", "https://www.mozilla.org/thunderbird/le
#endif
// Base URL for web-based support pages.
pref("app.support.baseURL", "https://support.thunderbird.net/%LOCALE%/%APP%/%APPBUILDID%/");
pref("app.support.baseURL", "https://support.thunderbird.net/%APP%/%VERSION%/%OS%/%LOCALE%/");
// Base url for web-based feedback pages.
pref("app.feedback.baseURL", "https://input.mozilla.org/%LOCALE%/feedback/%APP%/%VERSION%/");
......
......@@ -28,9 +28,6 @@ customElements.whenDefined("autocomplete-input").then(() => {
const { GlodaIMSearcher } = ChromeUtils.import(
"resource:///modules/search_im.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
/**
* The MozGlodaAutocompleteInput widget is used to display the autocomplete search bar.
......@@ -80,17 +77,6 @@ customElements.whenDefined("autocomplete-input").then(() => {
super.connectedCallback();
this.setAttribute("is", "gloda-autocomplete-input");
XPCOMUtils.defineLazyPreferenceGetter(
this,
"glodaEnabled",
"mailnews.database.global.indexer.enabled",
true,
(pref, oldVal, newVal) => {
this.toggleAttribute("hidden", !newVal);
}
);
this.glodaCompleter = null;
// @implements {nsIObserver}
......@@ -175,8 +161,6 @@ customElements.whenDefined("autocomplete-input").then(() => {
"autocomplete-did-enter-text"
);
this.toggleAttribute("hidden", !this.glodaEnabled);
// make sure we set our emptytext here from the get-go
if (this.hasAttribute("placeholder")) {
this.placeholder = this.getAttribute("placeholder");
......
......@@ -2137,12 +2137,13 @@
});
input.addEventListener("keyup", event => {
// Trigger the onRecipientsChanged method for every letter typed in
// order to properly update the "Send" button and trigger the save as
// draft prompt even before the creation of any pill.
// Trigger the onRecipientsChanged method for every letter typed or
// deleted in order to properly update the "Send" button and trigger
// the save as draft prompt even before the creation of any pill.
if (
event.key.length == 1 ||
(event.key.length > 1 && /[^a-zA-Z0-9]/.test(event.key))
(event.key.length > 1 && /[^a-zA-Z0-9]/.test(event.key)) ||
["Backspace", "Delete"].includes(event.key)
) {
onRecipientsChanged(false);
}
......
......@@ -325,7 +325,7 @@
title="&glodaSearch.title;"
align="center"
flex="1"
class="chromeclass-toolbar-additional">
class="gloda-search-widget chromeclass-toolbar-additional">
<image class="search-icon"/>
<html:input is="gloda-autocomplete-input" id="searchInput"
type="text"
......
......@@ -682,6 +682,22 @@ function OnLoadMessenger() {
document.getElementById("multimessage")
);
// Depending on the pref, hide/show the gloda toolbar search widgets.
XPCOMUtils.defineLazyPreferenceGetter(
this,
"gGlodaEnabled",
"mailnews.database.global.indexer.enabled",
true,
(pref, oldVal, newVal) => {
for (let widget of document.querySelectorAll(".gloda-search-widget")) {
widget.hidden = !newVal;
}
}
);
for (let widget of document.querySelectorAll(".gloda-search-widget")) {
widget.hidden = !this.gGlodaEnabled;
}
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
// Set up the appmenus. (This has to happen after the DOM has loaded.)
......
......@@ -886,6 +886,30 @@ var ExtensionsUI = {
});
}
// Reject add-ons using the legacy API. We cannot use the general "ignore
// unknown APIs" policy, as add-ons using the Legacy API from TB68 will
// not do anything, confusing the user.
if (data.manifest.legacy) {
let subject = {
wrappedJSObject: {
browser,
originatingURI: null,
installs: [
{
addon: info.addon,
name: info.addon.name,
error: 0,
},
],
install: null,
cancel: null,
},
};
Services.obs.notifyObservers(subject, "addon-install-failed");
info.reject();
return;
}
this.showPermissionsPrompt(browser, strings, info.icon, histkey).then(
answer => {
if (answer) {
......
......@@ -248,6 +248,12 @@ const ThemeVariableMap = [
lwtProperty: "sidebar_border",
},
],
[
"--sidebar-highlight-border-color",
{
lwtProperty: "sidebar_highlight_border",
},
],
];
const ThemeContentPropertyList = [
......
......@@ -611,7 +611,7 @@
<label crop="center" flex="1" class="displayText"></label>
<label class="dateTime"></label>
<button class="recover mini-button"
tooltiptext="&cmd.recover.label"
tooltiptext="&cmd.recover.label;"
cmd="cmd_recover"
ondblclick="event.stopPropagation();"
oncommand="activity.recoveryHandler.recover(activity);">
......
......@@ -11,6 +11,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]
......
/* 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, 102);
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, 102);
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");
Assert.equal(davDirectory.readOnly, false);
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;
}
let readOnlyInput = dialogDocument.getElementById("carddav-readOnly");
Assert.equal(readOnlyInput.checked, expectedValues.readOnly);
if ("readOnly" in newValues) {
readOnlyInput.checked = newValues.readOnly;
}
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,
readOnly: false,
},
{},
"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");
Assert.equal(davDirectory.readOnly, false);
info("Open the dialog and accept it. Nothing should change.");
await subtest(
{
name: "props",
url: URL_VALUE,
refreshActive: false,
refreshInterval: 30,
readOnly: false,
},
{},
"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");
Assert.equal(davDirectory.readOnly, false);
info("Open the dialog and change the values.");
await subtest(
{
name: "props",
url: URL_VALUE,
refreshActive: false,
refreshInterval: 30,
readOnly: false,
},
{
name: "CardDAV Properties Test",
refreshActive: true,
refreshInterval: 30,
readOnly: true,
},
"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;
Assert.equal(davDirectory.readOnly, true);
info("Open the dialog and accept it. Nothing should change.");
await subtest(
{
name: "CardDAV Properties Test",
url: URL_VALUE,
refreshActive: true,
refreshInterval: 30,
readOnly: true,
},
{},
"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"
);
Assert.equal(davDirectory.readOnly, true);
info("Open the dialog and change the interval.");
await subtest(
{
name: "CardDAV Properties Test",
url: URL_VALUE,
refreshActive: true,
refreshInterval: 30,
readOnly: true,
},
{ 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"
);
Assert.equal(davDirectory.readOnly, true);
});
......@@ -4036,7 +4036,9 @@ function ComposeLoad() {
attachmentBucketMarkEmptyBucket();
updateAriaLabelsAndTooltipsOfAllAddressRows();
for (let input of document.querySelectorAll(".address-input")) {
for (let input of document.querySelectorAll(
".address-input[recipienttype]"
)) {
input.onBeforeHandleKeyDown = event =>
addressInputOnBeforeHandleKeyDown(event);
}
......@@ -4896,7 +4898,7 @@ function updateSendLock() {
`input[is="autocomplete-input"][recipienttype]`
);
if (input.value.trim() && isValidAddress(input.value.trim())) {
if (input?.value.trim() && isValidAddress(input.value.trim())) {
gSendLocked = false;
break;
}
......@@ -4965,7 +4967,7 @@ function pillifyRecipients() {
);
// If we find a leftover string in the input field, create a pill. If the
// newly created pill is not a valid address, the sending will stop.
if (input.value.trim()) {
if (input?.value.trim()) {
recipientAddPills(input);
}
}
......@@ -7481,9 +7483,16 @@ function fromKeyPress(event) {
row.querySelector(`input[is="autocomplete-input"][recipienttype]`).focus();
}
/**
* Handle the keypress event of the subject line.
*
* @param {Event} event - The DOM Event.
*/
function subjectKeyPress(event) {
gSubjectChanged = true;
if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
// Move the focus to the body only if the Enter key is pressed without any
// modifier, as that would mean the user wants to send the message.
if (event.key == "Enter" && !event.ctrlKey && !event.metaKey) {
SetMsgBodyFrameFocus();
}
}
......
......@@ -562,6 +562,12 @@ function otherHeaderInputOnKeyDown(event) {
event.preventDefault();
SetFocusOnNextAvailableElement(input);
}
// macOS only variation to prevent the cursor from moving to the next
// element while sending.
if (event.metaKey) {
event.stopPropagation();
}
break;
case "Backspace":
case "Delete":
......@@ -708,6 +714,14 @@ function addressInputOnBeforeHandleKeyDown(event) {
event.preventDefault();
SetFocusOnNextAvailableElement(input);
}
// macOS only variation necessary to send messages since autocomplete
// input fields prevent that by default.
if (event.metaKey) {
// Prevent the focus from moving to the next element.
event.stopPropagation();
goDoCommand("cmd_sendWithCheck");
}
break;
case "Tab":
......
......@@ -33,6 +33,7 @@ messenger.jar:
content/messenger/parent/ext-messages.js (parent/ext-messages.js)
content/messenger/parent/ext-pkcs11.js (../../../../browser/components/extensions/parent/ext-pkcs11.js)
content/messenger/parent/ext-tabs.js (parent/ext-tabs.js)
content/messenger/parent/ext-theme.js (parent/ext-theme.js)
content/messenger/parent/ext-windows.js (parent/ext-windows.js)
content/messenger/schemas/accounts.json (schemas/accounts.json)
......@@ -53,4 +54,8 @@ messenger.jar:
content/messenger/schemas/messages.json (schemas/messages.json)
content/messenger/schemas/pkcs11.json (../../../../browser/components/extensions/schemas/pkcs11.json)
content/messenger/schemas/tabs.json (schemas/tabs.json)
content/messenger/schemas/theme.json (schemas/theme.json)
content/messenger/schemas/windows.json (schemas/windows.json)
% override chrome://extensions/content/schemas/theme.json chrome://messenger/content/schemas/theme.json
% override chrome://extensions/content/parent/ext-theme.js chrome://messenger/content/parent/ext-theme.js
/* 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";
/* global windowTracker, EventManager, EventEmitter */
/* eslint-disable complexity */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm"
);
var { getWinUtils } = ExtensionUtils;
const onUpdatedEmitter = new EventEmitter();
// Represents an empty theme for convenience of use
const emptyTheme = {
details: { colors: null, images: null, properties: null },
};
let defaultTheme = emptyTheme;
// Map[windowId -> Theme instance]
let windowOverrides = new Map();
/**
* Class representing either a global theme affecting all windows or an override on a specific window.
* Any extension updating the theme with a new global theme will replace the singleton defaultTheme.
*/
class Theme {
/**
* Creates a theme instance.
*
* @param {string} extension - Extension that created the theme.
* @param {Integer} windowId - The windowId where the theme is applied.
*/
constructor({
extension,
details,
darkDetails,
windowId,
experiment,
startupData,
}) {
this.extension = extension;
this.details = details;
this.darkDetails = darkDetails;
this.windowId = windowId;
if (startupData && startupData.lwtData) {
Object.assign(this, startupData);
} else {
// TODO: Update this part after bug 1550090
this.lwtStyles = {};
this.lwtDarkStyles = null;
if (darkDetails) {
this.lwtDarkStyles = {};
}
if (experiment) {
if (extension.experimentsAllowed) {