--- a/.eslintignore
+++ b/.eslintignore
@@ -44,17 +44,20 @@ mailnews/news/*
mailnews/test/*
# mailnews/extensions exclusions
mailnews/extensions/*
!mailnews/extensions/newsblog
# mail exclusions
mail/app/**
-mail/base/**
+mail/base/content/**
+mail/base/modules/**
+!mail/base/modules/ExtensionsUI.jsm
+mail/base/test/**
mail/branding/**
mail/config/**
mail/extensions/**
mail/installer/**
mail/locales/**
mail/test/**
mail/themes/**
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -58,16 +58,17 @@
%msgViewPickerDTD;
]>
<!--
- The 'what you think of when you think of thunderbird' window;
- 3-pane view inside of tabs.
-->
<window id="messengerWindow"
+ xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&titledefault.label;@PRE_RELEASE_SUFFIX@"
titlemodifier="&titledefault.label;@PRE_RELEASE_SUFFIX@"
titlemenuseparator="&titleSeparator.label;"
defaultTabTitle="&defaultTabTitle.label;"
onload="OnLoadMessenger()"
onunload="OnUnloadMessenger()"
@@ -300,16 +301,46 @@
</menupopup>
<tooltip id="tabmail-tabs-tooltip" onpopupshowing="document.getElementById('tabmail').createTooltip(event);"/>
<tooltip id="folderpopup" class="folderSummaryPopup"/>
<tooltip id="aHTMLTooltip" page="true"/>
<tooltip id="tabmail-tabs-tooltip" onpopupshowing="document.getElementById('tabmail').createTooltip(event);"/>
+ <panel id="notification-popup"
+ type="arrow"
+ position="after_start"
+ orient="vertical"
+ noautofocus="true"
+ role="alert"/>
+
+ <popupnotification id="addon-progress-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <progressmeter id="addon-progress-notification-progressmeter"/>
+ <label id="addon-progress-notification-progresstext" crop="end"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="addon-install-confirmation-notification" hidden="true">
+ <popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
+ </popupnotification>
+
+ <popupnotification id="addon-webext-permissions-notification" hidden="true">
+ <popupnotificationcontent class="addon-webext-perm-notification-content" orient="vertical">
+ <description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
+ <label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
+ <html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="addon-installed-notification" hidden="true">
+ <popupnotificationcontent class="addon-installed-notification-content" orient="vertical"/>
+ </popupnotification>
+
#include editContactPanel.inc
#include ../../components/im/content/chat-menu.inc
</popupset>
#ifdef XP_MACOSX
<popupset>
<menupopup id="menu_mac_dockmenu">
<menuitem label="&writeNewMessageDock.label;" id="tasksWriteNewMessage"
oncommand="writeNewMessageDock();"/>
@@ -357,16 +388,25 @@
class="tabmail-tab" crop="end" linkedpanel="mailContent"/>
</tabs>
<!-- Use of this element for extensions is deprecated! Current
extensions should add to #mail-toolbox and add a toolbar item to
#tabbar-toolbar below. -->
<hbox id="tabmail-buttons"/>
+ <box id="notification-popup-box"
+ align="center"
+ hidden="true">
+ <image id="addons-notification-icon"
+ src="chrome://mozapps/skin/extensions/extensionGeneric-16.svg"
+ class="notification-anchor-icon install-icon"
+ role="button"/>
+ </box>
+
<toolbar id="tabbar-toolbar" toolboxid="mail-toolbox"
context="toolbar-context-menu"
customizable="true"
mode="icons" defaultmode="icons" lockmode="true"
iconsize="small" defaulticonsize="small" lockiconsize="true"
defaultset=""/>
<toolbarbutton class="toolbarbutton-1 tabs-alltabs-button"
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -19,16 +19,36 @@ ChromeUtils.import("resource:///modules/
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
});
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() {
+ let tmp = {};
+ ChromeUtils.import("resource://gre/modules/PopupNotifications.jsm", tmp);
+ try {
+ // Hide all notifications while the URL is being edited and the address bar
+ // has focus, including the virtual focus in the results popup.
+ // We also have to hide notifications explicitly when the window is
+ // minimized because of the effects of the "noautohide" attribute on Linux.
+ // This can be removed once bug 545265 and bug 1320361 are fixed.
+ let shouldSuppress = () => window.windowState == window.STATE_MINIMIZED;
+ return new tmp.PopupNotifications(document.getElementById("tabmail"),
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"),
+ { shouldSuppress });
+ } catch (ex) {
+ Cu.reportError(ex);
+ return null;
+ }
+});
+
/* This is where functions related to the 3 pane window are kept */
// from MailNewsTypes.h
var nsMsgKey_None = 0xFFFFFFFF;
var nsMsgViewIndex_None = 0xFFFFFFFF;
var kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
var kStandardPaneConfig = 0;
--- a/mail/base/content/specialTabs.js
+++ b/mail/base/content/specialTabs.js
@@ -591,18 +591,16 @@ var specialTabs = {
request.onerror = onDownloadError;
request.timeout = this.REQUEST_TIMEOUT;
request.ontimeout = onDownloadError;
request.send(null);
},
// This will open any special tabs if necessary on startup.
openSpecialTabsOnStartup: function() {
- window.addEventListener("unload", specialTabs.onunload);
-
let browser = document.getElementById("dummycontentbrowser");
// Manually hook up session and global history for the first browser
// so that we don't have to load global history before bringing up a
// window.
// Wire up session and global history before any possible
// progress notifications for back/forward button updating
browser.docShell.initSessionHistory();
@@ -614,18 +612,16 @@ var specialTabs = {
// enable global history
try {
browser.docShell.useGlobalHistory = true;
} catch(ex) {
Cu.reportError("Places database may be locked: " + ex);
}
- Services.obs.addObserver(specialTabs, "mail-startup-done");
-
let tabmail = document.getElementById('tabmail');
tabmail.registerTabType(this.contentTabType);
tabmail.registerTabType(this.chromeTabType);
// If we've upgraded (note: always get these values so that we set
// the mstone preference for the new version):
let [fromVer, toVer] = this.getApplicationUpgradeVersions();
@@ -1343,236 +1339,16 @@ var specialTabs = {
// Save the function we'll use as listener so we can remove it later.
aTab.closeListener = onDOMWindowClose;
// Add the listener.
aTab.browser.addEventListener("DOMWindowClose",
aTab.closeListener, true);
}
},
- observe: function (aSubject, aTopic, aData) {
- if (aTopic != "mail-startup-done")
- return;
-
- Services.obs.removeObserver(specialTabs, "mail-startup-done");
- Services.obs.addObserver(this.xpInstallObserver, "addon-install-disabled");
- Services.obs.addObserver(this.xpInstallObserver, "addon-install-blocked");
- Services.obs.addObserver(this.xpInstallObserver, "addon-install-failed");
- Services.obs.addObserver(this.xpInstallObserver, "addon-install-confirmation");
- Services.obs.addObserver(this.xpInstallObserver, "addon-install-complete");
- },
-
- onunload: function () {
- window.removeEventListener("unload", specialTabs.onunload);
-
- Services.obs.removeObserver(specialTabs.xpInstallObserver, "addon-install-disabled");
- Services.obs.removeObserver(specialTabs.xpInstallObserver, "addon-install-blocked");
- Services.obs.removeObserver(specialTabs.xpInstallObserver, "addon-install-failed");
- Services.obs.removeObserver(specialTabs.xpInstallObserver, "addon-install-confirmation");
- Services.obs.removeObserver(specialTabs.xpInstallObserver, "addon-install-complete");
- },
-
- xpInstallObserver: {
- observe: function (aSubject, aTopic, aData) {
- let brandBundle = document.getElementById("bundle_brand");
- let messengerBundle = document.getElementById("bundle_messenger");
-
- let installInfo = aSubject.wrappedJSObject;
- let browser = installInfo.browser;
- let notificationBox = getNotificationBox(browser.contentWindow);
- let notificationID = aTopic;
- let brandShortName = brandBundle.getString("brandShortName");
- let notificationName, messageString, buttons;
- const iconURL = "chrome://mozapps/skin/extensions/extensionGeneric-16.svg";
-
- switch (aTopic) {
- case "addon-install-disabled":
- notificationID = "xpinstall-disabled";
-
- if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
- messageString = messengerBundle.getString("xpinstallDisabledMessageLocked");
- buttons = [];
- }
- else {
- messageString = messengerBundle.getString("xpinstallDisabledMessage");
-
- buttons = [{
- label: messengerBundle.getString("xpinstallDisabledButton"),
- accessKey: messengerBundle.getString("xpinstallDisabledButton.accesskey"),
- popup: null,
- callback: function editPrefs() {
- Services.prefs.setBoolPref("xpinstall.enabled", true);
- return false;
- }
- }];
- }
- if (notificationBox && !notificationBox.getNotificationWithValue(notificationID)) {
- notificationBox.appendNotification(messageString, notificationID,
- iconURL,
- notificationBox.PRIORITY_CRITICAL_HIGH,
- buttons);
- }
- break;
- case "addon-install-blocked":
- messageString =
- messengerBundle.getFormattedString("xpinstallPromptWarning",
- [brandShortName, installInfo.originatingURI.host]);
-
- buttons = [{
- label: messengerBundle.getString("xpinstallPromptAllowButton"),
- accessKey: messengerBundle.getString("xpinstallPromptAllowButton.accesskey"),
- popup: null,
- callback: function() {
- installInfo.install();
- }
- }];
-
- if (notificationBox && !notificationBox.getNotificationWithValue(notificationName)) {
- notificationBox.appendNotification(messageString, notificationName,
- iconURL,
- notificationBox.PRIORITY_WARNING_MEDIUM,
- buttons);
- }
- break;
- case "addon-install-failed":
- // XXX TODO This isn't terribly ideal for the multiple failure case
- for (let install of installInfo.installs) {
- let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
- installInfo.originatingURI.host;
- if (!host)
- host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
- install.sourceURI.host;
-
- let error = (host || install.error == 0) ?
- "addonError" : "addonLocalError";
- if (install.error != 0)
- error += install.error;
- else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
- error += "Blocklisted";
- else
- error += "Incompatible";
-
- messageString = messengerBundle.getString(error);
- messageString = messageString.replace("#1", install.name);
- if (host)
- messageString = messageString.replace("#2", host);
- messageString = messageString.replace("#3", brandShortName);
- messageString = messageString.replace("#4", Services.appinfo.version);
-
- if (notificationBox && !notificationBox.getNotificationWithValue(notificationID)) {
- notificationBox.appendNotification(messageString,
- notificationID,
- iconURL,
- notificationBox.PRIORITY_CRITICAL_HIGH,
- []);
- }
- }
- break;
- case "addon-install-confirmation":
- let acceptInstallation = () => {
- for (let install of installInfo.installs)
- install.install();
- installInfo = null;
- };
-
- let cancelInstallation = () => {
- if (installInfo) {
- for (let install of installInfo.installs) {
- // The notification may have been closed because the add-ons got
- // cancelled elsewhere, only try to cancel those that are still
- // pending install.
- if (install.state != AddonManager.STATE_CANCELLED)
- install.cancel();
- }
- }
- };
-
- messageString = messengerBundle.getString("addonConfirmInstall.message");
- messageString = PluralForm.get(installInfo.installs.length, messageString);
- messageString = messageString.replace("#1", brandShortName);
- messageString = messageString.replace("#2", installInfo.installs.length);
- messageString += " " + installInfo.installs.map(ii => ii.name).join(", ");
-
- buttons = [{
- label: messengerBundle.getString("addonConfirmInstall.installButton.label"),
- accessKey: messengerBundle.getString("addonConfirmInstall.installButton.accesskey"),
- callback: acceptInstallation,
- }, {
- label: messengerBundle.getString("addonConfirmInstall.cancelButton.label"),
- accessKey: messengerBundle.getString("addonConfirmInstall.cancelButton.accesskey"),
- callback: cancelInstallation,
- }];
-
- if (notificationBox)
- notificationBox.appendNotification(messageString,
- notificationID,
- iconURL,
- notificationBox.PRIORITY_WARNING_MEDIUM,
- buttons);
- break;
- case "addon-install-complete":
- let needsRestart = installInfo.installs.some(function(i) {
- return i.addon.pendingOperations != AddonManager.PENDING_NONE;
- });
-
- if (needsRestart) {
- messageString = messengerBundle.getString("addonsInstalledNeedsRestart");
- buttons = [{
- label: messengerBundle.getString("addonInstallRestartButton"),
- accessKey: messengerBundle.getString("addonInstallRestartButton.accesskey"),
- popup: null,
- callback: function() {
- BrowserUtils.restartApplication();
- }
- }];
- } else if (browser.currentURI.spec == "about:addons") {
- messageString = messengerBundle.getString("addonsInstalled");
- buttons = [];
- } else {
- messageString = messengerBundle.getString("addonsInstalled");
- buttons = [{
- label: messengerBundle.getString("addonInstallManage"),
- accessKey: messengerBundle.getString("addonInstallManage.accesskey"),
- popup: null,
- callback: function() {
- // Calculate the add-on type that is most popular in the list of
- // installs.
- let types = {};
- let bestType = null;
- for (let install of installInfo.installs) {
- if (install.type in types)
- types[install.type]++;
- else
- types[install.type] = 1;
-
- if (!bestType || types[install.type] > types[bestType])
- bestType = install.type;
-
- openAddonsMgr("addons://list/" + bestType);
- }
- }
- }];
- }
-
- messageString = PluralForm.get(installInfo.installs.length, messageString);
- messageString = messageString.replace("#1", installInfo.installs[0].name);
- messageString = messageString.replace("#2", installInfo.installs.length);
- messageString = messageString.replace("#3", brandShortName);
-
- if (notificationBox)
- notificationBox.appendNotification(messageString,
- notificationID,
- iconURL,
- notificationBox.PRIORITY_INFO_MEDIUM,
- buttons);
- break;
- }
- }
- },
-
/**
* Determine if we should load fav icons or not.
*
* @param aURI An nsIURI containing the current url.
*/
_shouldLoadFavIcon: function shouldLoadFavIcon(aURI) {
return (aURI &&
Services.prefs.getBoolPref("browser.chrome.site_icons") &&
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -1135,16 +1135,18 @@
return this.currentTabInfo;
]]></getter>
<setter><![CDATA[
this.switchToTab(val);
]]></setter>
</property>
+ <property name="selectedBrowser" onget="return this.getBrowserForSelectedTab();" />
+
<!-- getBrowserForSelectedTab is required as some toolkit functions
require a getBrowser() function. -->
<method name="getBrowserForSelectedTab">
<body><![CDATA[
if (!this.currentTabInfo)
this.currentTabInfo = this.tabInfo[0];
let tab = this.currentTabInfo;
new file mode 100644
--- /dev/null
+++ b/mail/base/modules/ExtensionsUI.jsm
@@ -0,0 +1,424 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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/. */
+
+this.EXPORTED_SYMBOLS = [];
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
+ PluralForm: "resource://gre/modules/PluralForm.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+ setTimeout: "resource://gre/modules/Timer.jsm",
+ StringBundle: "resource:///modules/StringBundle.js",
+});
+
+XPCOMUtils.defineLazyGetter(this, "addonsBundle", function() {
+ return new StringBundle("chrome://messenger/locale/addons.properties");
+});
+XPCOMUtils.defineLazyGetter(this, "brandBundle", function() {
+ return new StringBundle("chrome://branding/locale/brand.properties");
+});
+
+function getNotification(id, browser) {
+ return browser.ownerGlobal.PopupNotifications.getNotification(id, browser);
+}
+
+function showNotification(browser, ...args) {
+ let notifications = browser.ownerGlobal.PopupNotifications;
+ return notifications.show(browser, ...args);
+}
+
+// Removes a doorhanger notification if all of the installs it was notifying
+// about have ended in some way.
+function removeNotificationOnEnd(notification, installs) {
+ let count = installs.length;
+
+ function maybeRemove(install) {
+ install.removeListener(this);
+
+ if (--count == 0) {
+ // Check that the notification is still showing
+ let current = getNotification(notification.id, notification.browser);
+ if (current === notification)
+ notification.remove();
+ }
+ }
+
+ for (let install of installs) {
+ install.addListener({
+ onDownloadCancelled: maybeRemove,
+ onDownloadFailed: maybeRemove,
+ onInstallFailed: maybeRemove,
+ onInstallEnded: maybeRemove,
+ });
+ }
+}
+
+var gXPInstallObserver = {
+ pendingInstalls: new WeakMap(),
+
+ showInstallConfirmation(browser, installInfo, height = undefined) {
+ let document = browser.ownerDocument;
+ // If the confirmation notification is already open cache the installInfo
+ // and the new confirmation will be shown later
+ if (getNotification("addon-install-confirmation", browser)) {
+ let pending = this.pendingInstalls.get(browser);
+ if (pending) {
+ pending.push(installInfo);
+ } else {
+ this.pendingInstalls.set(browser, [installInfo]);
+ }
+ return;
+ }
+
+ let showNextConfirmation = () => {
+ // Make sure the browser is still alive.
+ // if (!gBrowser.browsers.includes(browser))
+ // return;
+
+ let pending = this.pendingInstalls.get(browser);
+ if (pending && pending.length)
+ this.showInstallConfirmation(browser, pending.shift());
+ };
+
+ // If all installs have already been cancelled in some way then just show
+ // the next confirmation
+ if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
+ showNextConfirmation();
+ return;
+ }
+
+ const anchorID = "addons-notification-icon";
+
+ // Make notifications persistent
+ var options = {
+ displayURI: installInfo.originatingURI,
+ persistent: true,
+ hideClose: true,
+ };
+
+ let acceptInstallation = () => {
+ for (let install of installInfo.installs)
+ install.install();
+ installInfo = null;
+ };
+
+ let cancelInstallation = () => {
+ if (installInfo) {
+ for (let install of installInfo.installs) {
+ // The notification may have been closed because the add-ons got
+ // cancelled elsewhere, only try to cancel those that are still
+ // pending install.
+ if (install.state != AddonManager.STATE_CANCELLED)
+ install.cancel();
+ }
+ }
+
+ showNextConfirmation();
+ };
+
+ options.eventCallback = event => {
+ switch (event) {
+ case "removed":
+ cancelInstallation();
+ break;
+ case "shown":
+ let addonList = document.getElementById("addon-install-confirmation-content");
+ while (addonList.firstChild)
+ addonList.firstChild.remove();
+
+ for (let install of installInfo.installs) {
+ let container = document.createXULElement("hbox");
+
+ let name = document.createXULElement("label");
+ name.setAttribute("value", install.addon.name);
+ name.setAttribute("class", "addon-install-confirmation-name");
+ container.appendChild(name);
+
+ addonList.appendChild(container);
+ }
+ break;
+ }
+ };
+
+ options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+
+ let messageString;
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ messageString = addonsBundle.getString("addonConfirmInstall.message");
+ notification.removeAttribute("warning");
+ options.learnMoreURL += "find-and-install-add-ons";
+
+ let brandShortName = brandBundle.getString("brandShortName");
+
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", brandShortName);
+ messageString = messageString.replace("#2", installInfo.installs.length);
+
+ let action = {
+ label: addonsBundle.getString("addonInstall.acceptButton2.label"),
+ accessKey: addonsBundle.getString("addonInstall.acceptButton2.accesskey"),
+ callback: acceptInstallation,
+ };
+
+ let secondaryAction = {
+ label: addonsBundle.getString("addonInstall.cancelButton.label"),
+ accessKey: addonsBundle.getString("addonInstall.cancelButton.accesskey"),
+ callback: () => {},
+ };
+
+ if (height) {
+ notification.style.minHeight = height + "px";
+ }
+
+ let popup = showNotification(browser, "addon-install-confirmation", messageString, anchorID,
+ action, [secondaryAction], options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ },
+
+ observe(subject, topic, data) {
+ let installInfo = subject.wrappedJSObject;
+ let browser = installInfo.browser;
+ let window = browser.ownerGlobal;
+
+ const anchorID = "addons-notification-icon";
+ var messageString, action;
+ var brandShortName = brandBundle.getString("brandShortName");
+
+ var notificationID = topic;
+ // Make notifications persistent
+ var options = {
+ displayURI: installInfo.originatingURI,
+ persistent: true,
+ hideClose: true,
+ timeout: Date.now() + 30000,
+ };
+
+ switch (topic) {
+ case "addon-install-disabled": {
+ notificationID = "xpinstall-disabled";
+ let secondaryActions = null;
+
+ if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
+ messageString = addonsBundle.getString("xpinstallDisabledMessageLocked");
+ } else {
+ messageString = addonsBundle.getString("xpinstallDisabledMessage");
+
+ action = {
+ label: addonsBundle.getString("xpinstallDisabledButton"),
+ accessKey: addonsBundle.getString("xpinstallDisabledButton.accesskey"),
+ callback: function editPrefs() {
+ Services.prefs.setBoolPref("xpinstall.enabled", true);
+ },
+ };
+
+ secondaryActions = [{
+ label: addonsBundle.getString("addonInstall.cancelButton.label"),
+ accessKey: addonsBundle.getString("addonInstall.cancelButton.accesskey"),
+ callback: () => {},
+ }];
+ }
+
+ showNotification(browser, notificationID, messageString, anchorID,
+ action, secondaryActions, options);
+ break;
+ }
+ case "addon-install-origin-blocked": {
+ messageString = addonsBundle.getFormattedString("xpinstallPromptMessage", [brandShortName]);
+
+ options.removeOnDismissal = true;
+ options.persistent = false;
+
+ let popup = showNotification(browser, notificationID, messageString, anchorID,
+ null, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break;
+ }
+ case "addon-install-blocked": {
+ messageString = addonsBundle.getFormattedString("xpinstallPromptMessage", [brandShortName]);
+
+ action = {
+ label: addonsBundle.getString("xpinstallPromptAllowButton"),
+ accessKey: addonsBundle.getString("xpinstallPromptAllowButton.accesskey"),
+ callback() {
+ installInfo.install();
+ },
+ };
+ let secondaryAction = {
+ label: addonsBundle.getString("xpinstallPromptMessage.dontAllow"),
+ accessKey: addonsBundle.getString("xpinstallPromptMessage.dontAllow.accesskey"),
+ callback: () => {
+ for (let install of installInfo.installs) {
+ if (install.state != AddonManager.STATE_CANCELLED) {
+ install.cancel();
+ }
+ }
+ },
+ };
+
+ let popup = showNotification(browser, notificationID, messageString, anchorID,
+ action, [secondaryAction], options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break;
+ }
+ case "addon-install-started": {
+ let needsDownload = function needsDownload(install) {
+ return install.state != AddonManager.STATE_DOWNLOADED;
+ };
+ // If all installs have already been downloaded then there is no need to
+ // show the download progress
+ if (!installInfo.installs.some(needsDownload))
+ return;
+ notificationID = "addon-progress";
+ messageString = addonsBundle.getString("addonDownloadingAndVerifying");
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", installInfo.installs.length);
+ options.installs = installInfo.installs;
+ options.contentWindow = browser.contentWindow;
+ options.sourceURI = browser.currentURI;
+ options.eventCallback = function(event) {
+ switch (event) {
+ case "shown":
+ let notificationElement = [...this.owner.panel.children]
+ .find(n => n.notification == this);
+ if (notificationElement) {
+ notificationElement.setAttribute("mainactiondisabled", "true");
+ }
+ break;
+ case "removed":
+ options.contentWindow = null;
+ options.sourceURI = null;
+ break;
+ }
+ };
+ action = {
+ label: addonsBundle.getString("addonInstall.acceptButton2.label"),
+ accessKey: addonsBundle.getString("addonInstall.acceptButton2.accesskey"),
+ callback: () => {},
+ };
+ let secondaryAction = {
+ label: addonsBundle.getString("addonInstall.cancelButton.label"),
+ accessKey: addonsBundle.getString("addonInstall.cancelButton.accesskey"),
+ callback: () => {
+ for (let install of installInfo.installs) {
+ if (install.state != AddonManager.STATE_CANCELLED) {
+ install.cancel();
+ }
+ }
+ },
+ };
+ let notification = showNotification(browser, notificationID, messageString, anchorID,
+ action, [secondaryAction], options);
+ notification._startTime = Date.now();
+ break;
+ }
+ case "addon-install-failed": {
+ options.removeOnDismissal = true;
+ options.persistent = false;
+
+ // TODO This isn't terribly ideal for the multiple failure case
+ for (let install of installInfo.installs) {
+ let host;
+ try {
+ host = options.displayURI.host;
+ } catch (e) {
+ // displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
+ }
+
+ if (!host)
+ host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
+ install.sourceURI.host;
+
+ let error = (host || install.error == 0) ? "addonInstallError" : "addonLocalInstallError";
+ let args;
+ if (install.error < 0) {
+ error += install.error;
+ args = [brandShortName, install.name];
+ } else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ error += "Blocklisted";
+ args = [install.name];
+ } else {
+ error += "Incompatible";
+ args = [brandShortName, Services.appinfo.version, install.name];
+ }
+
+ messageString = addonsBundle.getFormattedString(error, [args]);
+
+ showNotification(browser, notificationID, messageString, anchorID,
+ action, null, options);
+
+ // Can't have multiple notifications with the same ID, so stop here.
+ break;
+ }
+ this._removeProgressNotification(browser);
+ break;
+ }
+ case "addon-install-confirmation": {
+ let showNotification = () => {
+ let height = undefined;
+
+ if (window.PopupNotifications.isPanelOpen) {
+ let rect = browser.ownerDocument.getElementById("addon-progress-notification")
+ .getBoundingClientRect();
+ height = rect.height;
+ }
+
+ this._removeProgressNotification(browser);
+ this.showInstallConfirmation(browser, installInfo, height);
+ };
+
+ let progressNotification = getNotification("addon-progress", browser);
+ if (progressNotification) {
+ let downloadDuration = Date.now() - progressNotification._startTime;
+ let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay");
+ if (securityDelay - downloadDuration > 0) {
+ setTimeout(() => {
+ // The download may have been cancelled during the security delay
+ if (getNotification("addon-progress", browser))
+ showNotification();
+ }, securityDelay);
+ break;
+ }
+ }
+ showNotification();
+ break;
+ }
+ case "addon-install-complete": {
+ let secondaryActions = null;
+ let numAddons = installInfo.installs.length;
+
+ if (numAddons == 1) {
+ messageString = addonsBundle.getFormattedString("addonInstalled",
+ [installInfo.installs[0].name]);
+ } else {
+ messageString = addonsBundle.getString("addonsGenericInstalled");
+ messageString = PluralForm.get(numAddons, messageString);
+ messageString = messageString.replace("#1", numAddons);
+ }
+ action = null;
+
+ options.removeOnDismissal = true;
+ options.persistent = false;
+
+ showNotification(browser, notificationID, messageString, anchorID,
+ action, secondaryActions, options);
+ break;
+ }
+ }
+ },
+ _removeProgressNotification(browser) {
+ let notification = getNotification("addon-progress", browser);
+ if (notification)
+ notification.remove();
+ },
+};
+
+Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
+Services.obs.addObserver(gXPInstallObserver, "addon-install-complete");
--- a/mail/base/modules/moz.build
+++ b/mail/base/modules/moz.build
@@ -3,16 +3,17 @@
# 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/.
EXTRA_JS_MODULES += [
'attachmentChecker.js',
'dbViewWrapper.js',
'displayNameUtils.js',
'distribution.js',
+ 'ExtensionsUI.jsm',
'MailConsts.js',
'mailInstrumentation.js',
'mailMigrator.js',
'MailUtils.js',
'mailViewManager.js',
'MsgHdrSyntheticView.js',
'oauth.jsm',
'quickFilterManager.js',
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/addons.properties
@@ -0,0 +1,77 @@
+xpinstallPromptMessage=%S prevented this site from asking you to install software on your computer.
+xpinstallPromptMessage.dontAllow=Don’t Allow
+xpinstallPromptMessage.dontAllow.accesskey=D
+xpinstallPromptAllowButton=Allow
+# Accessibility Note:
+# Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
+# See http://www.mozilla.org/access/keyboard/accesskey for details
+xpinstallPromptAllowButton.accesskey=A
+xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
+xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
+xpinstallDisabledButton=Enable
+xpinstallDisabledButton.accesskey=n
+
+# LOCALIZATION NOTE (addonPostInstall.message1)
+# %1$S is replaced with the localized named of the extension that was
+# just installed.
+# %2$S is replaced with the localized name of the application.
+addonPostInstall.message1=%1$S has been added to %2$S.
+addonPostInstall.okay.label=OK
+addonPostInstall.okay.key=O
+
+# LOCALIZATION NOTE (addonDownloadingAndVerifying):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
+addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
+addonDownloadVerifying=Verifying
+
+addonInstall.unsigned=(Unverified)
+addonInstall.cancelButton.label=Cancel
+addonInstall.cancelButton.accesskey=C
+addonInstall.acceptButton2.label=Add
+addonInstall.acceptButton2.accesskey=A
+
+# LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the number of add-ons being installed
+addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
+addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk.
+
+# LOCALIZATION NOTE (addonConfirmInstallSomeUnsigned.message):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the total number of add-ons being installed (at least 2)
+addonConfirmInstallSomeUnsigned.message=;Caution: This site would like to install #2 add-ons in #1, some of which are unverified. Proceed at your own risk.
+
+# LOCALIZATION NOTE (addonInstalled):
+# %S is the name of the add-on
+addonInstalled=%S has been installed successfully.
+# LOCALIZATION NOTE (addonsGenericInstalled):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of add-ons
+addonsGenericInstalled=#1 add-on has been installed successfully.;#1 add-ons have been installed successfully.
+
+# LOCALIZATION NOTE (addonInstallError-1, addonInstallError-2, addonInstallError-3, addonInstallError-4, addonInstallError-5, addonLocalInstallError-1, addonLocalInstallError-2, addonLocalInstallError-3, addonLocalInstallError-4, addonLocalInstallError-5):
+# %1$S is the application name, %2$S is the add-on name
+addonInstallError-1=The add-on could not be downloaded because of a connection failure.
+addonInstallError-2=The add-on could not be installed because it does not match the add-on %1$S expected.
+addonInstallError-3=The add-on downloaded from this site could not be installed because it appears to be corrupt.
+addonInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file.
+addonInstallError-5=%1$S has prevented this site from installing an unverified add-on.
+addonLocalInstallError-1=This add-on could not be installed because of a filesystem error.
+addonLocalInstallError-2=This add-on could not be installed because it does not match the add-on %1$S expected.
+addonLocalInstallError-3=This add-on could not be installed because it appears to be corrupt.
+addonLocalInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file.
+addonLocalInstallError-5=This add-on could not be installed because it has not been verified.
+
+# LOCALIZATION NOTE (addonInstallErrorIncompatible):
+# %1$S is the application name, %2$S is the application version, %3$S is the add-on name
+addonInstallErrorIncompatible=%3$S could not be installed because it is not compatible with %1$S %2$S.
+
+# LOCALIZATION NOTE (addonInstallErrorBlocklisted): %S is add-on name
+addonInstallErrorBlocklisted=%S could not be installed because it has a high risk of causing stability or security problems.
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -646,72 +646,16 @@ headerccFieldMe=Me
headerbccFieldMe=Me
expandAttachmentPaneTooltip=Show the attachment pane
collapseAttachmentPaneTooltip=Hide the attachment pane
# Shown when content tabs are being loaded.
loadingTab=Loading…
-# LOCALIZATION NOTE (xpinstallPromptWarning):
-# %1$S is replaced by brandShortName, %2$S is replaced by the host name of the
-# site.
-xpinstallPromptWarning=%1$S prevented the site (%2$S) from asking you to install software on your computer.
-xpinstallPromptAllowButton=Allow
-# LOCALIZATION NOTE (xpinstallPromptAllowButton.accesskey):
-# Accessibility Note:
-# Be sure you do not choose an accesskey that is used elsewhere in the active
-# context (e.g. main menu bar, submenu of the warning popup button).
-# See https://www.mozilla.org/access/keyboard/accesskey for details
-xpinstallPromptAllowButton.accesskey=A
-
-xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
-xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
-xpinstallDisabledButton=Enable
-xpinstallDisabledButton.accesskey=n
-
-# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
-# Semi-colon list of plural forms. See:
-# https://developer.mozilla.org/en/docs/Localization_and_Plurals
-# #1 first add-on's name, #2 number of add-ons, #3 application name
-addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
-addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
-addonInstallRestartButton=Restart Now
-addonInstallRestartButton.accesskey=R
-addonInstallManage=Open Add-ons Manager
-addonInstallManage.accesskey=O
-
-# LOCALIZATION NOTE (addonConfirmInstallMessage):
-# Semicolon-separated list of plural forms. See:
-# http://developer.mozilla.org/en/docs/Localization_and_Plurals
-# #1 is brandShortName
-# #2 is the number of add-ons being installed
-addonConfirmInstall.message=Allow the installation of the following add-on in #1:;Allow the installation of the following #2 add-ons in #1:
-addonConfirmInstall.cancelButton.label=Cancel
-addonConfirmInstall.cancelButton.accesskey=C
-addonConfirmInstall.installButton.label=Install
-addonConfirmInstall.installButton.accesskey=I
-
-# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
-# #1 is the add-on name, #2 is the host name, #3 is the application name
-# #4 is the application version
-addonError-1=The add-on could not be downloaded because of a connection failure on #2.
-addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
-addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
-addonError-4=#1 could not be installed because #3 cannot modify the needed file.
-
-# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
-# #1 is the add-on name, #3 is the application name, #4 is the application version
-addonLocalError-1=This add-on could not be installed because of a filesystem error.
-addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
-addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
-addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
-addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
-addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
-
confirmMsgDelete.title=Confirm Deletion
confirmMsgDelete.collapsed.desc=This will delete messages in collapsed threads. Are you sure you want to continue?
confirmMsgDelete.deleteNoTrash.desc=This will delete messages immediately, without saving a copy to Trash. Are you sure you want to continue?
confirmMsgDelete.deleteFromTrash.desc=This will permanently delete messages from Trash. Are you sure you want to continue?
confirmMsgDelete.dontAsk.label=Don't ask me again.
confirmMsgDelete.delete.label=Delete
mailServerLoginFailedTitle=Login Failed
--- a/mail/locales/jar.mn
+++ b/mail/locales/jar.mn
@@ -22,16 +22,17 @@
locale/@AB_CD@/messenger/aboutRights.properties (%chrome/messenger/aboutRights.properties)
locale/@AB_CD@/messenger/aboutSupportMail.dtd (%chrome/messenger/aboutSupportMail.dtd)
locale/@AB_CD@/messenger/aboutSupportMail.properties (%chrome/messenger/aboutSupportMail.properties)
locale/@AB_CD@/messenger/telemetry.properties (%chrome/messenger/telemetry.properties)
locale/@AB_CD@/messenger/accountCreation.dtd (%chrome/messenger/accountCreation.dtd)
locale/@AB_CD@/messenger/accountCreation.properties (%chrome/messenger/accountCreation.properties)
locale/@AB_CD@/messenger/accountCreationModel.properties (%chrome/messenger/accountCreationModel.properties)
locale/@AB_CD@/messenger/accountCreationUtil.properties (%chrome/messenger/accountCreationUtil.properties)
+ locale/@AB_CD@/messenger/addons.properties (%chrome/messenger/addons.properties)
locale/@AB_CD@/messenger/charsetTitles.properties (%chrome/messenger/charsetTitles.properties)
locale/@AB_CD@/messenger/customizeToolbar.dtd (%chrome/messenger/customizeToolbar.dtd)
locale/@AB_CD@/messenger/customizeToolbar.properties (%chrome/messenger/customizeToolbar.properties)
locale/@AB_CD@/messenger/viewSource.dtd (%chrome/messenger/viewSource.dtd)
locale/@AB_CD@/messenger/viewSource.properties (%chrome/messenger/viewSource.properties)
locale/@AB_CD@/messenger/datetimepicker.dtd (%chrome/messenger/datetimepicker.dtd)
locale/@AB_CD@/messenger/systemIntegrationDialog.dtd (%chrome/messenger/systemIntegrationDialog.dtd)
locale/@AB_CD@/messenger/virtualFolderProperties.dtd (%chrome/messenger/virtualFolderProperties.dtd)
--- a/mail/themes/shared/mail/messenger.css
+++ b/mail/themes/shared/mail/messenger.css
@@ -29,16 +29,24 @@
description.error {
color: #f00;
}
toolbar[printpreview="true"] {
-moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
}
+#notification-popup-box > image.notification-anchor-icon {
+ width: 16px;
+ height: 16px;
+ -moz-context-properties: fill, fill-opacity;
+ fill: var(--lwt-toolbarbutton-icon-fill, currentColor);
+ fill-opacity: var(--toolbarbutton-icon-fill-opacity);
+}
+
#tabbar-toolbar {
-moz-appearance: none;
padding: 0;
}
#tabbar-toolbar[customizing="true"] {
min-width: 16px;
min-height: 10px;