Bug 1774083 - Part 3: Allow Windows notifications to persist after dismissed by timeout. r=nalexander

Adds a GUID to the notification's launch attribute too verify a timeout dismissed event doesn't remain in the Action Center.

Remove event hiding to allow notifications to be acted upon after the page or browser is closed.

Differential Revision: https://phabricator.services.mozilla.com/D151741
This commit is contained in:
Nicholas Rishel
2022-08-02 19:40:39 +00:00
parent ccf09c982e
commit a10fe6ef6f

View File

@@ -34,18 +34,6 @@
namespace mozilla { namespace mozilla {
namespace widget { namespace widget {
typedef ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*, IInspectable*>
ToastActivationHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*,
ABI::Windows::UI::Notifications::ToastDismissedEventArgs*>
ToastDismissedHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*,
ABI::Windows::UI::Notifications::ToastFailedEventArgs*>
ToastFailedHandler;
using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::UI::Notifications; using namespace ABI::Windows::UI::Notifications;
@@ -53,6 +41,17 @@ using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers; using namespace Microsoft::WRL::Wrappers;
using namespace mozilla; using namespace mozilla;
// Needed to disambiguate internal and Windows `ToastNotification` classes.
typedef ABI::Windows::UI::Notifications::ToastNotification WinToastNotification;
typedef ITypedEventHandler<WinToastNotification*, IInspectable*>
ToastActivationHandler;
typedef ITypedEventHandler<WinToastNotification*, ToastDismissedEventArgs*>
ToastDismissedHandler;
typedef ITypedEventHandler<WinToastNotification*, ToastFailedEventArgs*>
ToastFailedHandler;
typedef Collections::IVectorView<WinToastNotification*>
IVectorView_ToastNotification;
NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener) NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener)
static bool SetNodeValueString(const nsString& aString, IXmlNode* node, static bool SetNodeValueString(const nsString& aString, IXmlNode* node,
@@ -192,7 +191,6 @@ void ToastNotificationHandler::UnregisterHandler() {
mNotification->remove_Dismissed(mDismissedToken); mNotification->remove_Dismissed(mDismissedToken);
mNotification->remove_Activated(mActivatedToken); mNotification->remove_Activated(mActivatedToken);
mNotification->remove_Failed(mFailedToken); mNotification->remove_Failed(mFailedToken);
mNotifier->Hide(mNotification.Get());
} }
mNotification = nullptr; mNotification = nullptr;
@@ -464,6 +462,25 @@ bool ToastNotificationHandler::CreateWindowsNotificationFromXml(
&mFailedToken); &mFailedToken);
NS_ENSURE_TRUE(SUCCEEDED(hr), false); NS_ENSURE_TRUE(SUCCEEDED(hr), false);
ComPtr<IToastNotification2> notification2;
hr = mNotification.As(&notification2);
// Add tag, needed to check if toast is still present in the action center
// when we receive a dismiss timeout.
//
// Note that the Web Notifications API also exposes a "tag" element which can
// be used to override a current notification. This should be emulated at the
// DOM level as allowing webpages to overwrite Windows tags directly allows
// pages to override each other's notifications.
nsIDToCString uuidString(nsID::GenerateUUID());
size_t len = strlen(uuidString.get());
MOZ_ASSERT(len == NSID_LENGTH - 1);
nsAutoString tag;
CopyASCIItoUTF16(nsDependentCSubstring(uuidString.get(), len), tag);
HString hTag;
hTag.Set(tag.get());
notification2->put_Tag(hTag.Get());
ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics = ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
GetToastNotificationManagerStatics(); GetToastNotificationManagerStatics();
NS_ENSURE_TRUE(SUCCEEDED(hr), false); NS_ENSURE_TRUE(SUCCEEDED(hr), false);
@@ -544,9 +561,89 @@ ToastNotificationHandler::OnActivate(IToastNotification* notification,
return S_OK; return S_OK;
} }
// A single toast message can receive multiple dismiss events, at most one for
// the popup and at most one for the action center. We can't simply count
// dismiss events as the user may have disabled either popups or action center
// notifications, therefore we have to check if the toast remains in the history
// (action center) to determine if the toast is fully dismissed.
static bool NotificationStillPresent(
ComPtr<IToastNotification>& current_notification, nsString& nsAumid) {
HRESULT hr = S_OK;
ComPtr<IToastNotification2> current_notification2;
hr = current_notification.As(&current_notification2);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
HString current_id;
hr = current_notification2->get_Tag(current_id.GetAddressOf());
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
ComPtr<IToastNotificationManagerStatics> manager =
GetToastNotificationManagerStatics();
if (!manager) {
return false;
}
ComPtr<IToastNotificationManagerStatics2> manager2;
hr = manager.As(&manager2);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
ComPtr<IToastNotificationHistory> history;
hr = manager2->get_History(&history);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
ComPtr<IToastNotificationHistory2> history2;
hr = history.As(&history2);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
HString aumid;
hr = aumid.Set(nsAumid.get());
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
ComPtr<IVectorView_ToastNotification> toasts;
hr = history2->GetHistoryWithId(aumid.Get(), &toasts);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
unsigned int hist_size;
hr = toasts->get_Size(&hist_size);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
for (unsigned int i = 0; i < hist_size; i++) {
ComPtr<IToastNotification> hist_toast;
hr = toasts->GetAt(i, &hist_toast);
if (NS_WARN_IF(FAILED(hr))) {
continue;
}
ComPtr<IToastNotification2> hist_toast2;
hr = hist_toast.As(&hist_toast2);
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
HString history_id;
hr = hist_toast2->get_Tag(history_id.GetAddressOf());
NS_ENSURE_TRUE(SUCCEEDED(hr), false);
// We can not directly compare IToastNotification objects; their IUnknown
// pointers should be equivalent but under inspection were not. Therefore we
// use the notification's tag instead.
if (current_id == history_id) {
return true;
}
}
return false;
}
HRESULT HRESULT
ToastNotificationHandler::OnDismiss(IToastNotification* notification, ToastNotificationHandler::OnDismiss(IToastNotification* notification,
IToastDismissedEventArgs* aArgs) { IToastDismissedEventArgs* aArgs) {
// AddRef as ComPtr doesn't own the object and shouldn't release.
notification->AddRef();
ComPtr<IToastNotification> comptrNotification(notification);
// Don't dismiss notifications when they are still in the action center. We
// can receive multiple dismiss events.
if (NotificationStillPresent(comptrNotification, mAumid)) {
return S_OK;
}
SendFinished(); SendFinished();
mBackend->RemoveHandler(mName, this); mBackend->RemoveHandler(mName, this);
return S_OK; return S_OK;