| 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} |
| */ |