1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ToastNotification.h"
12 #include <windows.foundation.h>
13 #include <wrl/client.h>
15 #include "ErrorList.h"
16 #include "mozilla/BasePrincipal.h"
17 #include "mozilla/Buffer.h"
18 #include "mozilla/dom/Promise.h"
19 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
20 #include "mozilla/ErrorResult.h"
21 #include "mozilla/mscom/COMWrappers.h"
22 #include "mozilla/mscom/Utils.h"
23 #include "mozilla/widget/WinRegistry.h"
24 #include "mozilla/Logging.h"
25 #include "mozilla/Services.h"
26 #include "mozilla/WidgetUtils.h"
27 #include "nsAppRunner.h"
28 #include "nsComponentManagerUtils.h"
30 #include "nsIObserverService.h"
31 #include "nsIWindowMediator.h"
32 #include "nsPIDOMWindow.h"
34 #include "nsThreadUtils.h"
35 #include "nsWindowsHelpers.h"
36 #include "nsXREDirProvider.h"
38 #include "ToastNotificationHandler.h"
39 #include "ToastNotificationHeaderOnlyUtils.h"
44 using namespace toastnotification
;
46 using namespace ABI::Windows::Foundation
;
47 using namespace Microsoft::WRL
;
48 using namespace Microsoft::WRL::Wrappers
;
49 // Needed to disambiguate internal and Windows `ToastNotification` classes.
50 using namespace ABI::Windows::UI::Notifications
;
51 using WinToastNotification
= ABI::Windows::UI::Notifications::ToastNotification
;
52 using IVectorView_ToastNotification
=
53 ABI::Windows::Foundation::Collections::IVectorView
<WinToastNotification
*>;
54 using IVectorView_ScheduledToastNotification
=
55 ABI::Windows::Foundation::Collections::IVectorView
<
56 ScheduledToastNotification
*>;
58 LazyLogModule
sWASLog("WindowsAlertsService");
60 NS_IMPL_ISUPPORTS(ToastNotification
, nsIAlertsService
, nsIWindowsAlertsService
,
61 nsIAlertsDoNotDisturb
, nsIObserver
)
63 ToastNotification::ToastNotification() = default;
65 ToastNotification::~ToastNotification() = default;
67 nsresult
ToastNotification::Init() {
68 if (!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
69 // Windows Toast Notification requires AppId. But allow `xpcshell` to
70 // create the service to test other functionality.
71 if (!EnsureAumidRegistered()) {
72 MOZ_LOG(sWASLog
, LogLevel::Warning
, ("Failed to register AUMID!"));
73 return NS_ERROR_NOT_IMPLEMENTED
;
76 MOZ_LOG(sWASLog
, LogLevel::Info
, ("Using dummy AUMID in xpcshell test"));
77 mAumid
.emplace(u
"XpcshellTestToastAumid"_ns
);
80 MOZ_LOG(sWASLog
, LogLevel::Info
,
81 ("Using AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid
.ref()).get()));
83 nsCOMPtr
<nsIObserverService
> obsServ
=
84 mozilla::services::GetObserverService();
87 NS_FAILED(obsServ
->AddObserver(this, "last-pb-context-exited", false)));
89 NS_FAILED(obsServ
->AddObserver(this, "quit-application", false)));
95 bool ToastNotification::EnsureAumidRegistered() {
96 // Check if this is an MSIX install, app identity is provided by the package
97 // so no registration is necessary.
98 if (AssignIfMsixAumid(mAumid
)) {
100 sWASLog
, LogLevel::Info
,
101 ("Found MSIX AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid
.ref()).get()));
105 nsAutoString installHash
;
106 nsresult rv
= gDirServiceProvider
->GetInstallHash(installHash
);
107 NS_ENSURE_SUCCESS(rv
, false);
109 // Check if toasts were registered during NSIS/MSI installation.
110 if (AssignIfNsisAumid(installHash
, mAumid
)) {
111 MOZ_LOG(sWASLog
, LogLevel::Info
,
112 ("Found AUMID from installer (with install hash '%s'): '%s'",
113 NS_ConvertUTF16toUTF8(installHash
).get(),
114 NS_ConvertUTF16toUTF8(mAumid
.ref()).get()));
118 // No AUMID registered, fall through to runtime registration for development
119 // and portable builds.
120 if (RegisterRuntimeAumid(installHash
, mAumid
)) {
122 sWASLog
, LogLevel::Info
,
123 ("Updated AUMID registration at runtime (for install hash '%s'): '%s'",
124 NS_ConvertUTF16toUTF8(installHash
).get(),
125 NS_ConvertUTF16toUTF8(mAumid
.ref()).get()));
129 MOZ_LOG(sWASLog
, LogLevel::Warning
,
130 ("Failed to register AUMID at runtime! (for install hash '%s')",
131 NS_ConvertUTF16toUTF8(installHash
).get()));
135 bool ToastNotification::AssignIfMsixAumid(Maybe
<nsAutoString
>& aAumid
) {
137 // ERROR_INSUFFICIENT_BUFFER signals that we're in an MSIX package, and
138 // therefore should use the package's AUMID.
139 if (GetCurrentApplicationUserModelId(&len
, nullptr) !=
140 ERROR_INSUFFICIENT_BUFFER
) {
141 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Not an MSIX package"));
144 mozilla::Buffer
<wchar_t> buffer(len
);
145 LONG success
= GetCurrentApplicationUserModelId(&len
, buffer
.Elements());
146 NS_ENSURE_TRUE(success
== ERROR_SUCCESS
, false);
148 aAumid
.emplace(buffer
.Elements());
152 bool ToastNotification::AssignIfNsisAumid(nsAutoString
& aInstallHash
,
153 Maybe
<nsAutoString
>& aAumid
) {
154 nsAutoString nsisAumidName
=
155 u
""_ns MOZ_TOAST_APP_NAME u
"Toast-"_ns
+ aInstallHash
;
156 nsAutoString nsisAumidPath
= u
"AppUserModelId\\"_ns
+ nsisAumidName
;
157 if (!WinRegistry::HasKey(HKEY_CLASSES_ROOT
, nsisAumidPath
)) {
158 MOZ_LOG(sWASLog
, LogLevel::Debug
,
159 ("No CustomActivator value from installer in key 'HKCR\\%s'",
160 NS_ConvertUTF16toUTF8(nsisAumidPath
).get()));
164 aAumid
.emplace(nsisAumidName
);
168 bool ToastNotification::RegisterRuntimeAumid(nsAutoString
& aInstallHash
,
169 Maybe
<nsAutoString
>& aAumid
) {
170 // Portable AUMID slightly differs from installed AUMID so we can
171 // differentiate installed to HKCU vs portable installs if necessary.
172 nsAutoString portableAumid
=
173 u
""_ns MOZ_TOAST_APP_NAME u
"PortableToast-"_ns
+ aInstallHash
;
175 nsCOMPtr
<nsIFile
> appdir
;
176 nsresult rv
= gDirServiceProvider
->GetGREDir()->Clone(getter_AddRefs(appdir
));
177 NS_ENSURE_SUCCESS(rv
, false);
179 nsCOMPtr
<nsIFile
> icon
;
180 rv
= appdir
->Clone(getter_AddRefs(icon
));
181 NS_ENSURE_SUCCESS(rv
, false);
183 // Add browser subdirectory only for Firefox
184 #ifdef MOZ_BUILD_APP_IS_BROWSER
185 rv
= icon
->Append(u
"browser"_ns
);
186 NS_ENSURE_SUCCESS(rv
, false);
189 rv
= icon
->Append(u
"VisualElements"_ns
);
190 NS_ENSURE_SUCCESS(rv
, false);
192 rv
= icon
->Append(u
"VisualElements_70.png"_ns
);
193 NS_ENSURE_SUCCESS(rv
, false);
195 nsAutoString iconPath
;
196 rv
= icon
->GetPath(iconPath
);
197 NS_ENSURE_SUCCESS(rv
, false);
199 nsCOMPtr
<nsIFile
> comDll
;
200 rv
= appdir
->Clone(getter_AddRefs(comDll
));
201 NS_ENSURE_SUCCESS(rv
, false);
203 rv
= comDll
->Append(u
"notificationserver.dll"_ns
);
204 NS_ENSURE_SUCCESS(rv
, false);
206 nsAutoString dllPath
;
207 rv
= comDll
->GetPath(dllPath
);
208 NS_ENSURE_SUCCESS(rv
, false);
211 // Manipulate the registry using a transaction so that any failures are
213 wchar_t transactionName
[] = L
"" MOZ_TOAST_APP_NAME L
" toast registration";
214 txn
.own(::CreateTransaction(nullptr, nullptr, TRANSACTION_DO_NOT_PROMOTE
, 0,
215 0, 0, transactionName
));
216 NS_ENSURE_TRUE(txn
.get() != INVALID_HANDLE_VALUE
, false);
220 auto RegisterKey
= [&](const nsAString
& path
, nsAutoRegKey
& key
) {
222 status
= ::RegCreateKeyTransactedW(
223 HKEY_CURRENT_USER
, PromiseFlatString(path
).get(), 0, nullptr,
224 REG_OPTION_NON_VOLATILE
, KEY_ALL_ACCESS
, nullptr, &rawKey
, nullptr, txn
,
226 NS_ENSURE_TRUE(status
== ERROR_SUCCESS
, false);
231 auto RegisterValue
= [&](nsAutoRegKey
& key
, const nsAString
& name
,
232 unsigned long type
, const nsAString
& data
) {
233 status
= ::RegSetValueExW(
234 key
, PromiseFlatString(name
).get(), 0, type
,
235 static_cast<const BYTE
*>(PromiseFlatString(data
).get()),
236 (data
.Length() + 1) * sizeof(wchar_t));
238 return status
== ERROR_SUCCESS
;
242 /* Writes the following keys and values to the registry.
243 * HKEY_CURRENT_USER\Software\Classes\AppID\{GUID} DllSurrogate : REG_SZ = ""
244 * \AppUserModelId\{MOZ_TOAST_APP_NAME}PortableToast-{install hash} CustomActivator : REG_SZ = {GUID}
245 * DisplayName : REG_EXPAND_SZ = {display name}
246 * IconUri : REG_EXPAND_SZ = {icon path}
247 * \CLSID\{GUID} AppID : REG_SZ = {GUID}
248 * \InprocServer32 (Default) : REG_SZ = {notificationserver.dll path}
252 constexpr nsLiteralString classes
= u
"Software\\Classes\\"_ns
;
254 nsAutoString aumid
= classes
+ u
"AppUserModelId\\"_ns
+ portableAumid
;
255 nsAutoRegKey aumidKey
;
256 NS_ENSURE_TRUE(RegisterKey(aumid
, aumidKey
), false);
258 nsAutoString guidStr
;
260 DWORD bufferSizeBytes
= NSID_LENGTH
* sizeof(wchar_t);
261 Buffer
<wchar_t> guidBuffer(bufferSizeBytes
);
262 status
= ::RegGetValueW(HKEY_CURRENT_USER
, aumid
.get(), L
"CustomActivator",
263 RRF_RT_REG_SZ
, 0, guidBuffer
.Elements(),
267 if (status
== ERROR_SUCCESS
&&
268 SUCCEEDED(CLSIDFromString(guidBuffer
.Elements(), &unused
))) {
269 guidStr
= guidBuffer
.Elements();
271 nsIDToCString
uuidString(nsID::GenerateUUID());
272 size_t len
= strlen(uuidString
.get());
273 MOZ_ASSERT(len
== NSID_LENGTH
- 1);
274 CopyASCIItoUTF16(nsDependentCSubstring(uuidString
.get(), len
), guidStr
);
277 if (status
== ERROR_SUCCESS
) {
278 MOZ_LOG(sWASLog
, LogLevel::Debug
,
279 ("Existing CustomActivator guid found: '%s'",
280 NS_ConvertUTF16toUTF8(guidStr
).get()));
282 MOZ_LOG(sWASLog
, LogLevel::Debug
,
283 ("New CustomActivator guid generated: '%s'",
284 NS_ConvertUTF16toUTF8(guidStr
).get()));
288 RegisterValue(aumidKey
, u
"CustomActivator"_ns
, REG_SZ
, guidStr
), false);
289 nsAutoString brandName
;
290 WidgetUtils::GetBrandShortName(brandName
);
292 RegisterValue(aumidKey
, u
"DisplayName"_ns
, REG_EXPAND_SZ
, brandName
),
295 RegisterValue(aumidKey
, u
"IconUri"_ns
, REG_EXPAND_SZ
, iconPath
), false);
297 nsAutoString appid
= classes
+ u
"AppID\\"_ns
+ guidStr
;
298 nsAutoRegKey appidKey
;
299 NS_ENSURE_TRUE(RegisterKey(appid
, appidKey
), false);
300 NS_ENSURE_TRUE(RegisterValue(appidKey
, u
"DllSurrogate"_ns
, REG_SZ
, u
""_ns
),
303 nsAutoString clsid
= classes
+ u
"CLSID\\"_ns
+ guidStr
;
304 nsAutoRegKey clsidKey
;
305 NS_ENSURE_TRUE(RegisterKey(clsid
, clsidKey
), false);
306 NS_ENSURE_TRUE(RegisterValue(clsidKey
, u
"AppID"_ns
, REG_SZ
, guidStr
), false);
308 nsAutoString inproc
= clsid
+ u
"\\InprocServer32"_ns
;
309 nsAutoRegKey inprocKey
;
310 NS_ENSURE_TRUE(RegisterKey(inproc
, inprocKey
), false);
311 // Set the component's path to this DLL
312 NS_ENSURE_TRUE(RegisterValue(inprocKey
, u
""_ns
, REG_SZ
, dllPath
), false);
314 NS_ENSURE_TRUE(::CommitTransaction(txn
), false);
317 sWASLog
, LogLevel::Debug
,
318 ("Updated registration for CustomActivator value in key 'HKCU\\%s': '%s'",
319 NS_ConvertUTF16toUTF8(aumid
).get(),
320 NS_ConvertUTF16toUTF8(guidStr
).get()));
321 aAumid
.emplace(portableAumid
);
325 nsresult
ToastNotification::BackgroundDispatch(nsIRunnable
* runnable
) {
326 return NS_DispatchBackgroundTask(runnable
);
330 ToastNotification::GetSuppressForScreenSharing(bool* aRetVal
) {
331 *aRetVal
= mSuppressForScreenSharing
;
336 ToastNotification::SetSuppressForScreenSharing(bool aSuppress
) {
337 mSuppressForScreenSharing
= aSuppress
;
342 ToastNotification::Observe(nsISupports
* aSubject
, const char* aTopic
,
343 const char16_t
* aData
) {
344 nsDependentCString
topic(aTopic
);
346 for (auto iter
= mActiveHandlers
.Iter(); !iter
.Done(); iter
.Next()) {
347 RefPtr
<ToastNotificationHandler
> handler
= iter
.UserData();
349 auto removeNotification
= [&]() {
350 // The handlers' destructors will do the right thing (de-register with
354 // Break the cycle between the handler and the MSCOM notification so the
355 // handler's destructor will be called.
356 handler
->UnregisterHandler();
359 if (topic
== "last-pb-context-exited"_ns
) {
360 if (handler
->IsPrivate()) {
361 handler
->HideAlert();
362 removeNotification();
364 } else if (topic
== "quit-application"_ns
) {
365 removeNotification();
373 ToastNotification::ShowAlertNotification(
374 const nsAString
& aImageUrl
, const nsAString
& aAlertTitle
,
375 const nsAString
& aAlertText
, bool aAlertTextClickable
,
376 const nsAString
& aAlertCookie
, nsIObserver
* aAlertListener
,
377 const nsAString
& aAlertName
, const nsAString
& aBidi
, const nsAString
& aLang
,
378 const nsAString
& aData
, nsIPrincipal
* aPrincipal
, bool aInPrivateBrowsing
,
379 bool aRequireInteraction
) {
380 nsCOMPtr
<nsIAlertNotification
> alert
=
381 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID
);
382 if (NS_WARN_IF(!alert
)) {
383 return NS_ERROR_FAILURE
;
385 // vibrate is unused for now
386 nsTArray
<uint32_t> vibrate
;
387 nsresult rv
= alert
->Init(aAlertName
, aImageUrl
, aAlertTitle
, aAlertText
,
388 aAlertTextClickable
, aAlertCookie
, aBidi
, aLang
,
389 aData
, aPrincipal
, aInPrivateBrowsing
,
390 aRequireInteraction
, false, vibrate
);
391 if (NS_WARN_IF(NS_FAILED(rv
))) {
394 return ShowAlert(alert
, aAlertListener
);
398 ToastNotification::SetManualDoNotDisturb(bool aDoNotDisturb
) {
399 return NS_ERROR_NOT_IMPLEMENTED
;
403 ToastNotification::GetManualDoNotDisturb(bool* aRet
) {
404 return NS_ERROR_NOT_IMPLEMENTED
;
408 ToastNotification::ShowAlert(nsIAlertNotification
* aAlert
,
409 nsIObserver
* aAlertListener
) {
410 NS_ENSURE_ARG(aAlert
);
412 if (mSuppressForScreenSharing
) {
417 MOZ_TRY(aAlert
->GetCookie(cookie
));
420 MOZ_TRY(aAlert
->GetName(name
));
423 MOZ_TRY(aAlert
->GetTitle(title
));
424 if (!EnsureUTF16Validity(title
)) {
425 MOZ_LOG(sWASLog
, LogLevel::Warning
,
426 ("Notification title was invalid UTF16, unpaired surrogates have "
431 MOZ_TRY(aAlert
->GetText(text
));
432 if (!EnsureUTF16Validity(text
)) {
433 MOZ_LOG(sWASLog
, LogLevel::Warning
,
434 ("Notification text was invalid UTF16, unpaired surrogates have "
439 MOZ_TRY(aAlert
->GetTextClickable(&textClickable
));
442 MOZ_TRY(aAlert
->GetSilent(&isSilent
));
444 nsAutoString hostPort
;
445 MOZ_TRY(aAlert
->GetSource(hostPort
));
447 nsAutoString opaqueRelaunchData
;
448 MOZ_TRY(aAlert
->GetOpaqueRelaunchData(opaqueRelaunchData
));
450 bool requireInteraction
;
451 MOZ_TRY(aAlert
->GetRequireInteraction(&requireInteraction
));
453 bool inPrivateBrowsing
;
454 MOZ_TRY(aAlert
->GetInPrivateBrowsing(&inPrivateBrowsing
));
456 nsTArray
<RefPtr
<nsIAlertAction
>> actions
;
457 MOZ_TRY(aAlert
->GetActions(actions
));
459 nsCOMPtr
<nsIPrincipal
> principal
;
460 MOZ_TRY(aAlert
->GetPrincipal(getter_AddRefs(principal
)));
461 bool isSystemPrincipal
= principal
&& principal
->IsSystemPrincipal();
463 bool handleActions
= false;
464 auto imagePlacement
= ImagePlacement::eInline
;
465 if (isSystemPrincipal
) {
466 nsCOMPtr
<nsIWindowsAlertNotification
> winAlert(do_QueryInterface(aAlert
));
468 MOZ_TRY(winAlert
->GetHandleActions(&handleActions
));
470 nsIWindowsAlertNotification::ImagePlacement placement
;
471 MOZ_TRY(winAlert
->GetImagePlacement(&placement
));
473 case nsIWindowsAlertNotification::eHero
:
474 imagePlacement
= ImagePlacement::eHero
;
476 case nsIWindowsAlertNotification::eIcon
:
477 imagePlacement
= ImagePlacement::eIcon
;
479 case nsIWindowsAlertNotification::eInline
:
480 imagePlacement
= ImagePlacement::eInline
;
483 MOZ_LOG(sWASLog
, LogLevel::Error
,
484 ("Invalid image placement enum value: %hhu", placement
));
485 return NS_ERROR_UNEXPECTED
;
490 RefPtr
<ToastNotificationHandler
> oldHandler
= mActiveHandlers
.Get(name
);
492 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
493 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
494 this, mAumid
.ref(), aAlertListener
, name
, cookie
, title
, text
, hostPort
,
495 textClickable
, requireInteraction
, actions
, isSystemPrincipal
,
496 opaqueRelaunchData
, inPrivateBrowsing
, isSilent
, handleActions
,
498 mActiveHandlers
.InsertOrUpdate(name
, RefPtr
{handler
});
500 MOZ_LOG(sWASLog
, LogLevel::Debug
,
501 ("Adding handler '%s': [%p] (now %d handlers)",
502 NS_ConvertUTF16toUTF8(name
).get(), handler
.get(),
503 mActiveHandlers
.Count()));
505 nsresult rv
= handler
->InitAlertAsync(aAlert
);
506 if (NS_WARN_IF(NS_FAILED(rv
))) {
507 MOZ_LOG(sWASLog
, LogLevel::Debug
,
508 ("Failed to init alert, removing '%s'",
509 NS_ConvertUTF16toUTF8(name
).get()));
510 mActiveHandlers
.Remove(name
);
511 handler
->UnregisterHandler();
515 // If there was a previous handler with the same name then unregister it.
517 oldHandler
->UnregisterHandler();
524 ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification
* aAlert
,
525 const nsAString
& aWindowsTag
,
526 nsAString
& aString
) {
527 NS_ENSURE_ARG(aAlert
);
530 MOZ_TRY(aAlert
->GetCookie(cookie
));
533 MOZ_TRY(aAlert
->GetName(name
));
536 MOZ_TRY(aAlert
->GetTitle(title
));
539 MOZ_TRY(aAlert
->GetText(text
));
542 MOZ_TRY(aAlert
->GetTextClickable(&textClickable
));
545 MOZ_TRY(aAlert
->GetSilent(&isSilent
));
547 nsAutoString hostPort
;
548 MOZ_TRY(aAlert
->GetSource(hostPort
));
550 nsAutoString opaqueRelaunchData
;
551 MOZ_TRY(aAlert
->GetOpaqueRelaunchData(opaqueRelaunchData
));
553 bool requireInteraction
;
554 MOZ_TRY(aAlert
->GetRequireInteraction(&requireInteraction
));
556 bool inPrivateBrowsing
;
557 MOZ_TRY(aAlert
->GetInPrivateBrowsing(&inPrivateBrowsing
));
559 nsTArray
<RefPtr
<nsIAlertAction
>> actions
;
560 MOZ_TRY(aAlert
->GetActions(actions
));
562 nsCOMPtr
<nsIPrincipal
> principal
;
563 MOZ_TRY(aAlert
->GetPrincipal(getter_AddRefs(principal
)));
564 bool isSystemPrincipal
= principal
&& principal
->IsSystemPrincipal();
566 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
567 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
568 this, mAumid
.ref(), nullptr /* aAlertListener */, name
, cookie
, title
,
569 text
, hostPort
, textClickable
, requireInteraction
, actions
,
570 isSystemPrincipal
, opaqueRelaunchData
, inPrivateBrowsing
, isSilent
);
572 // Usually, this will be empty during testing, making test output
574 MOZ_TRY(handler
->SetWindowsTag(aWindowsTag
));
576 nsAutoString imageURL
;
577 MOZ_TRY(aAlert
->GetImageURL(imageURL
));
579 return handler
->CreateToastXmlString(imageURL
, aString
);
582 // Verifies that the tag recieved associates to a notification created during
583 // this application's session, or handles fallback behavior.
584 RefPtr
<ToastHandledPromise
> ToastNotification::VerifyTagPresentOrFallback(
585 const nsAString
& aWindowsTag
) {
586 MOZ_LOG(sWASLog
, LogLevel::Debug
,
587 ("Iterating %d handlers", mActiveHandlers
.Count()));
589 for (auto iter
= mActiveHandlers
.Iter(); !iter
.Done(); iter
.Next()) {
590 RefPtr
<ToastNotificationHandler
> handler
= iter
.UserData();
592 nsresult rv
= handler
->GetWindowsTag(tag
);
594 if (NS_SUCCEEDED(rv
)) {
595 MOZ_LOG(sWASLog
, LogLevel::Debug
,
596 ("Comparing external windowsTag '%s' to handled windowsTag '%s'",
597 NS_ConvertUTF16toUTF8(aWindowsTag
).get(),
598 NS_ConvertUTF16toUTF8(tag
).get()));
599 if (aWindowsTag
.Equals(tag
)) {
600 MOZ_LOG(sWASLog
, LogLevel::Debug
,
601 ("External windowsTag '%s' is handled by handler [%p]",
602 NS_ConvertUTF16toUTF8(aWindowsTag
).get(), handler
.get()));
603 return ToastHandledPromise::CreateAndResolve(true, __func__
);
606 MOZ_LOG(sWASLog
, LogLevel::Debug
,
607 ("Failed to get windowsTag for handler [%p]", handler
.get()));
611 // Fallback handling is required.
613 MOZ_LOG(sWASLog
, LogLevel::Debug
,
614 ("External windowsTag '%s' is not handled",
615 NS_ConvertUTF16toUTF8(aWindowsTag
).get()));
617 RefPtr
<ToastHandledPromise::Private
> fallbackPromise
=
618 new ToastHandledPromise::Private(__func__
);
620 // TODO: Bug 1806005 - At time of writing this function is called in a call
621 // stack containing `WndProc` callback on an STA thread. As a result attempts
622 // to create a `ToastNotificationManager` instance results an an
623 // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM
624 // interface and synchronize the COM interactions if notification fallback
625 // handling were no longer handled in a `WndProc` context.
626 NS_DispatchBackgroundTask(NS_NewRunnableFunction(
627 "VerifyTagPresentOrFallback fallback background task",
628 [fallbackPromise
]() { fallbackPromise
->Resolve(false, __func__
); }));
630 return fallbackPromise
;
633 // Send our window's PID to the notification server so that it can grant us
634 // `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID.
635 // Absense of PID which may occur when we are yet unable to retrieve the
636 // window during startup, which is not a problem in practice as new windows
637 // receive focus by default.
638 void ToastNotification::SignalComNotificationHandled(
639 const nsAString
& aWindowsTag
) {
642 nsCOMPtr
<nsIWindowMediator
> winMediator(
643 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
));
645 nsCOMPtr
<mozIDOMWindowProxy
> navWin
;
646 winMediator
->GetMostRecentBrowserWindow(getter_AddRefs(navWin
));
648 nsCOMPtr
<nsIWidget
> widget
=
649 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin
));
651 HWND hwnd
= (HWND
)widget
->GetNativeData(NS_NATIVE_WINDOW
);
652 GetWindowThreadProcessId(hwnd
, &pid
);
654 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get widget"));
657 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get navWin"));
660 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get WinMediator"));
663 // Run pipe communication off the main thread to prevent UI jank from
664 // blocking. Nothing relies on the COM server's response or that it has
665 // responded at time of commit.
666 NS_DispatchBackgroundTask(
667 NS_NewRunnableFunction(
668 "SignalComNotificationHandled background task",
669 [pid
, aWindowsTag
= nsString
{aWindowsTag
}]() mutable {
670 std::wstring pipeName
= GetNotificationPipeName(aWindowsTag
.get());
673 pipe
.own(CreateFileW(pipeName
.c_str(), GENERIC_READ
| GENERIC_WRITE
,
674 0, nullptr, OPEN_EXISTING
,
675 FILE_FLAG_OVERLAPPED
, nullptr));
676 if (pipe
.get() == INVALID_HANDLE_VALUE
) {
677 MOZ_LOG(sWASLog
, LogLevel::Error
,
678 ("Unable to open notification server pipe."));
682 DWORD pipeFlags
= PIPE_READMODE_MESSAGE
;
683 if (!SetNamedPipeHandleState(pipe
.get(), &pipeFlags
, nullptr,
685 MOZ_LOG(sWASLog
, LogLevel::Error
,
686 ("Error setting pipe handle state, error %lu",
691 // Pass our window's PID to the COM server receive
692 // `SetForegroundWindow` permissions, and wait for a message
693 // acknowledging the permission has been granted.
694 ToastNotificationPidMessage in
{};
696 ToastNotificationPermissionMessage out
{};
697 auto transact
= [&](OVERLAPPED
& overlapped
) {
698 return TransactNamedPipe(pipe
.get(), &in
, sizeof(in
), &out
,
699 sizeof(out
), nullptr, &overlapped
);
702 SyncDoOverlappedIOWithTimeout(pipe
, sizeof(out
), transact
);
704 if (result
&& out
.setForegroundPermissionGranted
&& pid
!= 0) {
706 sWASLog
, LogLevel::Info
,
707 ("SetForegroundWindow permission granted to our window."));
709 MOZ_LOG(sWASLog
, LogLevel::Error
,
710 ("SetForegroundWindow permission not granted to our "
714 NS_DISPATCH_EVENT_MAY_BLOCK
);
718 ToastNotification::HandleWindowsTag(const nsAString
& aWindowsTag
,
719 JSContext
* aCx
, dom::Promise
** aPromise
) {
720 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
721 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD
);
724 RefPtr
<dom::Promise
> promise
=
725 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx
), rv
);
726 RETURN_NSRESULT_ON_FAILURE(rv
);
728 this->VerifyTagPresentOrFallback(aWindowsTag
)
730 GetMainThreadSerialEventTarget(), __func__
,
731 [aWindowsTag
= nsString(aWindowsTag
),
732 promise
](const bool aTagWasHandled
) {
733 // We no longer need to query toast information from OS and can
734 // allow the COM server to proceed (toast information is lost once
735 // the COM server's `Activate` callback returns).
736 SignalComNotificationHandled(aWindowsTag
);
739 if (NS_WARN_IF(!js
.Init(promise
->GetGlobalObject()))) {
740 promise
->MaybeReject(NS_ERROR_FAILURE
);
744 // Resolve the DOM Promise with a JS object. Set properties if
745 // fallback handling is necessary.
747 JSContext
* cx
= js
.cx();
748 JS::Rooted
<JSObject
*> obj(cx
, JS_NewPlainObject(cx
));
750 JS::Rooted
<JS::Value
> attVal(cx
, JS::BooleanValue(aTagWasHandled
));
751 Unused
<< NS_WARN_IF(
752 !JS_SetProperty(cx
, obj
, "tagWasHandled", attVal
));
754 promise
->MaybeResolve(obj
);
756 [aWindowsTag
= nsString(aWindowsTag
), promise
]() {
757 // We no longer need to query toast information from OS and can
758 // allow the COM server to proceed (toast information is lost once
759 // the COM server's `Activate` callback returns).
760 SignalComNotificationHandled(aWindowsTag
);
762 promise
->MaybeReject(NS_ERROR_FAILURE
);
765 promise
.forget(aPromise
);
770 ToastNotification::CloseAlert(const nsAString
& aAlertName
,
771 bool aContextClosed
) {
772 RefPtr
<ToastNotificationHandler
> handler
;
773 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
774 // This can happen when the handler is gone but the closure signal is not
775 // yet reached to the content process, and then the process tries closing
780 if (!aContextClosed
|| handler
->IsPrivate()) {
781 // Hide the alert when not implicitly closed by tab/window closing or when
782 // notification originated from a private tab.
783 handler
->HideAlert();
786 mActiveHandlers
.Remove(aAlertName
);
787 handler
->UnregisterHandler();
792 bool ToastNotification::IsActiveHandler(const nsAString
& aAlertName
,
793 ToastNotificationHandler
* aHandler
) {
794 RefPtr
<ToastNotificationHandler
> handler
;
795 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
798 return handler
== aHandler
;
801 void ToastNotification::RemoveHandler(const nsAString
& aAlertName
,
802 ToastNotificationHandler
* aHandler
) {
803 // The alert may have been replaced; only remove it from the active
804 // handler's map if it's the same.
805 if (IsActiveHandler(aAlertName
, aHandler
)) {
806 // Terrible things happen if the destructor of a handler is called inside
807 // the hashtable .Remove() method. Wait until we have returned from there.
808 RefPtr
<ToastNotificationHandler
> kungFuDeathGrip(aHandler
);
809 mActiveHandlers
.Remove(aAlertName
);
810 aHandler
->UnregisterHandler();
815 ToastNotification::RemoveAllNotificationsForInstall() {
818 ComPtr
<IToastNotificationManagerStatics
> manager
;
819 hr
= GetActivationFactory(
821 RuntimeClass_Windows_UI_Notifications_ToastNotificationManager
)
824 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
827 MOZ_ASSERT(mAumid
.isSome());
828 hr
= aumid
.Set(mAumid
.ref().get());
829 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
831 // Hide toasts in action center.
833 ComPtr
<IToastNotificationManagerStatics2
> manager2
;
834 hr
= manager
.As(&manager2
);
835 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
837 ComPtr
<IToastNotificationHistory
> history
;
838 hr
= manager2
->get_History(&history
);
839 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
841 hr
= history
->ClearWithId(aumid
.Get());
842 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
845 // Hide scheduled toasts.
847 ComPtr
<IToastNotifier
> notifier
;
848 hr
= manager
->CreateToastNotifierWithId(aumid
.Get(), ¬ifier
);
849 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
851 ComPtr
<IVectorView_ScheduledToastNotification
> scheduledToasts
;
852 hr
= notifier
->GetScheduledToastNotifications(&scheduledToasts
);
853 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
855 unsigned int schedSize
;
856 hr
= scheduledToasts
->get_Size(&schedSize
);
857 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
859 for (unsigned int i
= 0; i
< schedSize
; i
++) {
860 ComPtr
<IScheduledToastNotification
> schedToast
;
861 hr
= scheduledToasts
->GetAt(i
, &schedToast
);
862 if (NS_WARN_IF(FAILED(hr
))) {
866 hr
= notifier
->RemoveFromSchedule(schedToast
.Get());
867 Unused
<< NS_WARN_IF(FAILED(hr
));
874 NS_IMPL_ISUPPORTS_INHERITED(WindowsAlertNotification
, AlertNotification
,
875 nsIWindowsAlertNotification
)
878 WindowsAlertNotification::GetHandleActions(bool* aHandleActions
) {
879 *aHandleActions
= mHandleActions
;
884 WindowsAlertNotification::SetHandleActions(bool aHandleActions
) {
885 mHandleActions
= aHandleActions
;
889 NS_IMETHODIMP
WindowsAlertNotification::GetImagePlacement(
890 nsIWindowsAlertNotification::ImagePlacement
* aImagePlacement
) {
891 *aImagePlacement
= mImagePlacement
;
895 NS_IMETHODIMP
WindowsAlertNotification::SetImagePlacement(
896 nsIWindowsAlertNotification::ImagePlacement aImagePlacement
) {
897 switch (aImagePlacement
) {
901 mImagePlacement
= aImagePlacement
;
904 MOZ_LOG(sWASLog
, LogLevel::Error
,
905 ("Invalid image placement enum value: %hhu", aImagePlacement
));
906 return NS_ERROR_INVALID_ARG
;
912 } // namespace widget
913 } // namespace mozilla