Index: lib/notificationHelper.js |
=================================================================== |
--- a/lib/notificationHelper.js |
+++ b/lib/notificationHelper.js |
@@ -21,37 +21,64 @@ |
const {startIconAnimation, stopIconAnimation} = require("icon"); |
const {Utils} = require("utils"); |
const {Notification: NotificationStorage} = require("notification"); |
const {stringifyURL} = require("url"); |
const {initAntiAdblockNotification} = require("antiadblockInit"); |
const {Prefs} = require("prefs"); |
+// "Open" notifications are any notifications currently being displayed in the |
Manish Jethani
2017/08/12 16:57:48
Comments explaining the conceptual difference betw
kzar
2017/08/21 13:59:25
Thanks for adding those, I think it helps quite a
|
+// platform's notification center. There can be multiple notifications open at |
+// the same time. |
+let openNotificationsInfo = new Map(); |
+let openNotificationIds = new Set(); |
+ |
+// The "active" notification is the one that is displayed in the extension's |
+// popup. It may be the same as one of the open notifications, or it may be a |
+// different one. |
let activeNotification = null; |
-let activeButtons = null; |
+ |
let defaultDisplayMethods = ["popup"]; |
let displayMethods = Object.create(null); |
+ |
displayMethods.critical = ["icon", "notification", "popup"]; |
displayMethods.question = ["notification"]; |
displayMethods.normal = ["notification"]; |
displayMethods.relentless = ["notification"]; |
displayMethods.information = ["icon", "popup"]; |
-function prepareNotificationIconAndPopup() |
+function prepareNotificationIconAndPopup(notification) |
kzar
2017/08/21 13:59:24
Since we're no longer always making the given noti
Manish Jethani
2017/08/24 09:54:12
It seems appropriate to me, since the function wil
|
{ |
- let animateIcon = shouldDisplay("icon", activeNotification.type); |
- activeNotification.onClicked = () => |
+ let animateIcon = shouldDisplay("icon", notification.type); |
+ let showInPopup = shouldDisplay("popup", notification.type); |
+ |
+ if (animateIcon || showInPopup) |
{ |
+ // If another notification is active, close it first. This will stop any |
+ // existing icon animation. |
+ if (activeNotification) |
+ activeNotification.onClosed(); |
+ |
+ activeNotification = notification; |
+ } |
+ |
+ notification.onClosed = () => |
+ { |
+ if (notification != activeNotification) |
+ return; |
+ |
if (animateIcon) |
stopIconAnimation(); |
- notificationClosed(); |
+ |
+ activeNotification = null; |
}; |
+ |
if (animateIcon) |
- startIconAnimation(activeNotification.type); |
+ startIconAnimation(notification.type); |
} |
function getNotificationButtons(notificationType, message) |
{ |
let buttons = []; |
if (notificationType == "question") |
{ |
buttons.push({ |
@@ -94,165 +121,214 @@ |
title: ext.i18n.getMessage("notification_configure") |
}); |
} |
} |
return buttons; |
} |
-function openNotificationLinks() |
+function openNotificationLinks(notification) |
{ |
- if (activeNotification.links) |
+ if (notification.links) |
{ |
- for (let link of activeNotification.links) |
+ for (let link of notification.links) |
ext.pages.open(Utils.getDocLink(link)); |
} |
} |
-function notificationButtonClick(buttonIndex) |
+function notificationButtonClick(notificationInfo, buttonIndex) |
{ |
- if (!(activeButtons && buttonIndex in activeButtons)) |
+ let {notification, buttons} = notificationInfo || {}; |
+ |
+ if (!(buttons && buttonIndex in buttons)) |
return; |
- switch (activeButtons[buttonIndex].type) |
+ switch (buttons[buttonIndex].type) |
{ |
case "link": |
- ext.pages.open(Utils.getDocLink(activeNotification.links[buttonIndex])); |
+ ext.pages.open(Utils.getDocLink(notification.links[buttonIndex])); |
break; |
case "open-all": |
- openNotificationLinks(); |
+ openNotificationLinks(notification); |
break; |
case "configure": |
Prefs.notifications_showui = true; |
ext.showOptions(page => |
{ |
page.sendMessage({ |
type: "app.respond", |
action: "focusSection", |
args: ["notifications"] |
}); |
}); |
break; |
case "question": |
- NotificationStorage.triggerQuestionListeners(activeNotification.id, |
+ NotificationStorage.triggerQuestionListeners(notification.id, |
buttonIndex == 0); |
- NotificationStorage.markAsShown(activeNotification.id); |
- activeNotification.onClicked(); |
Manish Jethani
2017/08/12 16:57:48
Calling onClicked (now onClosed) is no longer need
|
+ NotificationStorage.markAsShown(notification.id); |
break; |
} |
} |
-function notificationClosed() |
+function notificationOpened(key, notificationInfo) |
+{ |
+ let {notification} = notificationInfo; |
+ |
+ openNotificationsInfo.set(key, notificationInfo); |
+ openNotificationIds.add(notification.id); |
+} |
+ |
+function notificationClosed(key) |
{ |
- activeNotification = null; |
+ let {notification} = openNotificationsInfo.get(key) || {}; |
+ if (notification) |
+ { |
+ if (notification.onClosed) |
+ notification.onClosed(); |
+ |
+ openNotificationIds.delete(notification.id); |
+ } |
+ |
+ openNotificationsInfo.delete(key); |
} |
function initChromeNotifications() |
{ |
// Chrome hides notifications in notification center when clicked so |
// we need to clear them. |
- function clearActiveNotification(notificationId) |
+ function clearNotification(key) |
{ |
- if (activeNotification && |
- activeNotification.type != "question" && |
- !("links" in activeNotification)) |
+ let {notification} = openNotificationsInfo.get(key) || {}; |
+ if (notification && |
+ notification.type != "question" && |
+ !("links" in notification)) |
return; |
- chrome.notifications.clear(notificationId, wasCleared => |
+ chrome.notifications.clear(key, wasCleared => |
Manish Jethani
2017/08/12 16:57:48
Renamed "notificationId" to "key" here, because th
|
{ |
if (wasCleared) |
- notificationClosed(); |
+ notificationClosed(key); |
}); |
} |
chrome.notifications.onButtonClicked.addListener( |
- (notificationId, buttonIndex) => |
+ (key, buttonIndex) => |
{ |
- notificationButtonClick(buttonIndex); |
- clearActiveNotification(notificationId); |
+ notificationButtonClick(openNotificationsInfo.get(key), buttonIndex); |
+ clearNotification(key); |
} |
); |
- chrome.notifications.onClicked.addListener(clearActiveNotification); |
+ |
+ chrome.notifications.onClicked.addListener(clearNotification); |
+ |
+ // Known issue on macOS: When the user closes a notification by clicking on |
Manish Jethani
2017/08/12 16:57:48
Maybe it's just me, but macOS doesn't seem to trig
|
+ // the close button in the notification widget's title bar, it does not |
+ // trigger the onClosed handler. |
chrome.notifications.onClosed.addListener(notificationClosed); |
} |
function showNotification(notification) |
{ |
- if (activeNotification && activeNotification.id == notification.id) |
+ if ((activeNotification && activeNotification.id == notification.id) || |
+ openNotificationIds.has(notification.id)) |
return; |
- activeNotification = notification; |
- if (shouldDisplay("notification", activeNotification.type)) |
+ if (shouldDisplay("notification", notification.type)) |
{ |
+ let notificationInfo = {notification}; |
+ |
let texts = NotificationStorage.getLocalizedTexts(notification); |
let title = texts.title || ""; |
let message = (texts.message || "").replace(/<\/?(a|strong)>/g, ""); |
let iconUrl = ext.getURL("icons/detailed/abp-128.png"); |
- let linkCount = (activeNotification.links || []).length; |
+ let linkCount = (notification.links || []).length; |
+ |
+ notificationInfo.buttons = getNotificationButtons(notification.type, |
+ texts.message); |
if ("notifications" in chrome) |
{ |
- activeButtons = getNotificationButtons(activeNotification.type, |
- texts.message); |
chrome.notifications.create("", { |
type: "basic", |
title, |
iconUrl, |
message, |
- buttons: activeButtons.map(button => ({title: button.title})), |
+ buttons: notificationInfo.buttons.map(button => |
+ ({title: button.title})), |
// We use the highest priority to prevent the notification |
// from closing automatically. |
- priority: 2 |
+ priority: 2, |
+ // Critical notifications should not go away in a few seconds, as |
Manish Jethani
2017/08/12 16:57:48
I took the liberty to add this here. If it's a cri
kzar
2017/08/21 13:59:24
Sounds sensible to me but you've not mentioned thi
Thomas Greiner
2017/08/22 18:16:59
I can confirm that critical notifications are not
Manish Jethani
2017/08/24 09:54:12
It's true that the notification doesn't close auto
Manish Jethani
2017/08/24 09:54:12
I've updated the issue now:
https://issues.adbloc
kzar
2017/08/29 12:16:30
Thanks
|
+ // happens on some platforms (e.g. macOS). |
+ requireInteraction: notification.type == "critical" |
+ }, |
+ key => |
+ { |
+ notificationOpened(key, notificationInfo); |
}); |
} |
- else if ("Notification" in window && activeNotification.type != "question") |
+ else if ("Notification" in window && notification.type != "question") |
{ |
if (linkCount > 0) |
message += " " + ext.i18n.getMessage("notification_without_buttons"); |
+ // Since the Notification API doesn't given us a "notification ID", and |
+ // we don't want to use the Notification object itself as the key |
+ // (because memory leaks), we generate a random key here. |
kzar
2017/08/21 13:59:24
Couldn't we rather use a WeakMap?
|
+ let key = String(Date.now() + Math.random()); |
+ |
let widget = new Notification( |
title, |
{ |
lang: Utils.appLocale, |
dir: ext.i18n.getMessage("@@bidi_dir"), |
body: message, |
icon: iconUrl |
} |
); |
- widget.addEventListener("click", openNotificationLinks); |
- widget.addEventListener("close", notificationClosed); |
+ notificationOpened(key, notificationInfo); |
+ |
+ widget.addEventListener("click", () => |
+ { |
+ openNotificationLinks(notification); |
+ }); |
+ |
+ widget.addEventListener("close", () => notificationClosed(key)); |
+ widget.addEventListener("error", () => notificationClosed(key)); |
} |
else |
{ |
message = title + "\n" + message; |
if (linkCount > 0) |
message += "\n\n" + ext.i18n.getMessage("notification_with_buttons"); |
let approved = confirm(message); |
- if (activeNotification.type == "question") |
- notificationButtonClick(approved ? 0 : 1); |
+ if (notification.type == "question") |
+ notificationButtonClick(notificationInfo, approved ? 0 : 1); |
else if (approved) |
- openNotificationLinks(); |
+ openNotificationLinks(notification); |
} |
} |
- prepareNotificationIconAndPopup(); |
- if (notification.type !== "question") |
+ prepareNotificationIconAndPopup(notification); |
+ |
+ if (notification.type != "question") |
NotificationStorage.markAsShown(notification.id); |
} |
/** |
* Initializes the notification system. |
*/ |
exports.initNotifications = () => |
{ |
if ("notifications" in chrome) |
initChromeNotifications(); |
+ |
initAntiAdblockNotification(); |
}; |
/** |
* Gets the active notification to be shown if any. |
* |
* @return {?object} |
*/ |