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 rv
= icon
->Append(u
"browser"_ns
);
184 NS_ENSURE_SUCCESS(rv
, false);
186 rv
= icon
->Append(u
"VisualElements"_ns
);
187 NS_ENSURE_SUCCESS(rv
, false);
189 rv
= icon
->Append(u
"VisualElements_70.png"_ns
);
190 NS_ENSURE_SUCCESS(rv
, false);
192 nsAutoString iconPath
;
193 rv
= icon
->GetPath(iconPath
);
194 NS_ENSURE_SUCCESS(rv
, false);
196 nsCOMPtr
<nsIFile
> comDll
;
197 rv
= appdir
->Clone(getter_AddRefs(comDll
));
198 NS_ENSURE_SUCCESS(rv
, false);
200 rv
= comDll
->Append(u
"notificationserver.dll"_ns
);
201 NS_ENSURE_SUCCESS(rv
, false);
203 nsAutoString dllPath
;
204 rv
= comDll
->GetPath(dllPath
);
205 NS_ENSURE_SUCCESS(rv
, false);
208 // Manipulate the registry using a transaction so that any failures are
210 wchar_t transactionName
[] = L
"" MOZ_TOAST_APP_NAME L
" toast registration";
211 txn
.own(::CreateTransaction(nullptr, nullptr, TRANSACTION_DO_NOT_PROMOTE
, 0,
212 0, 0, transactionName
));
213 NS_ENSURE_TRUE(txn
.get() != INVALID_HANDLE_VALUE
, false);
217 auto RegisterKey
= [&](const nsAString
& path
, nsAutoRegKey
& key
) {
219 status
= ::RegCreateKeyTransactedW(
220 HKEY_CURRENT_USER
, PromiseFlatString(path
).get(), 0, nullptr,
221 REG_OPTION_NON_VOLATILE
, KEY_ALL_ACCESS
, nullptr, &rawKey
, nullptr, txn
,
223 NS_ENSURE_TRUE(status
== ERROR_SUCCESS
, false);
228 auto RegisterValue
= [&](nsAutoRegKey
& key
, const nsAString
& name
,
229 unsigned long type
, const nsAString
& data
) {
230 status
= ::RegSetValueExW(
231 key
, PromiseFlatString(name
).get(), 0, type
,
232 static_cast<const BYTE
*>(PromiseFlatString(data
).get()),
233 (data
.Length() + 1) * sizeof(wchar_t));
235 return status
== ERROR_SUCCESS
;
239 /* Writes the following keys and values to the registry.
240 * HKEY_CURRENT_USER\Software\Classes\AppID\{GUID} DllSurrogate : REG_SZ = ""
241 * \AppUserModelId\{MOZ_TOAST_APP_NAME}PortableToast-{install hash} CustomActivator : REG_SZ = {GUID}
242 * DisplayName : REG_EXPAND_SZ = {display name}
243 * IconUri : REG_EXPAND_SZ = {icon path}
244 * \CLSID\{GUID} AppID : REG_SZ = {GUID}
245 * \InprocServer32 (Default) : REG_SZ = {notificationserver.dll path}
249 constexpr nsLiteralString classes
= u
"Software\\Classes\\"_ns
;
251 nsAutoString aumid
= classes
+ u
"AppUserModelId\\"_ns
+ portableAumid
;
252 nsAutoRegKey aumidKey
;
253 NS_ENSURE_TRUE(RegisterKey(aumid
, aumidKey
), false);
255 nsAutoString guidStr
;
257 DWORD bufferSizeBytes
= NSID_LENGTH
* sizeof(wchar_t);
258 Buffer
<wchar_t> guidBuffer(bufferSizeBytes
);
259 status
= ::RegGetValueW(HKEY_CURRENT_USER
, aumid
.get(), L
"CustomActivator",
260 RRF_RT_REG_SZ
, 0, guidBuffer
.Elements(),
264 if (status
== ERROR_SUCCESS
&&
265 SUCCEEDED(CLSIDFromString(guidBuffer
.Elements(), &unused
))) {
266 guidStr
= guidBuffer
.Elements();
268 nsIDToCString
uuidString(nsID::GenerateUUID());
269 size_t len
= strlen(uuidString
.get());
270 MOZ_ASSERT(len
== NSID_LENGTH
- 1);
271 CopyASCIItoUTF16(nsDependentCSubstring(uuidString
.get(), len
), guidStr
);
274 if (status
== ERROR_SUCCESS
) {
275 MOZ_LOG(sWASLog
, LogLevel::Debug
,
276 ("Existing CustomActivator guid found: '%s'",
277 NS_ConvertUTF16toUTF8(guidStr
).get()));
279 MOZ_LOG(sWASLog
, LogLevel::Debug
,
280 ("New CustomActivator guid generated: '%s'",
281 NS_ConvertUTF16toUTF8(guidStr
).get()));
285 RegisterValue(aumidKey
, u
"CustomActivator"_ns
, REG_SZ
, guidStr
), false);
286 nsAutoString brandName
;
287 WidgetUtils::GetBrandShortName(brandName
);
289 RegisterValue(aumidKey
, u
"DisplayName"_ns
, REG_EXPAND_SZ
, brandName
),
292 RegisterValue(aumidKey
, u
"IconUri"_ns
, REG_EXPAND_SZ
, iconPath
), false);
294 nsAutoString appid
= classes
+ u
"AppID\\"_ns
+ guidStr
;
295 nsAutoRegKey appidKey
;
296 NS_ENSURE_TRUE(RegisterKey(appid
, appidKey
), false);
297 NS_ENSURE_TRUE(RegisterValue(appidKey
, u
"DllSurrogate"_ns
, REG_SZ
, u
""_ns
),
300 nsAutoString clsid
= classes
+ u
"CLSID\\"_ns
+ guidStr
;
301 nsAutoRegKey clsidKey
;
302 NS_ENSURE_TRUE(RegisterKey(clsid
, clsidKey
), false);
303 NS_ENSURE_TRUE(RegisterValue(clsidKey
, u
"AppID"_ns
, REG_SZ
, guidStr
), false);
305 nsAutoString inproc
= clsid
+ u
"\\InprocServer32"_ns
;
306 nsAutoRegKey inprocKey
;
307 NS_ENSURE_TRUE(RegisterKey(inproc
, inprocKey
), false);
308 // Set the component's path to this DLL
309 NS_ENSURE_TRUE(RegisterValue(inprocKey
, u
""_ns
, REG_SZ
, dllPath
), false);
311 NS_ENSURE_TRUE(::CommitTransaction(txn
), false);
314 sWASLog
, LogLevel::Debug
,
315 ("Updated registration for CustomActivator value in key 'HKCU\\%s': '%s'",
316 NS_ConvertUTF16toUTF8(aumid
).get(),
317 NS_ConvertUTF16toUTF8(guidStr
).get()));
318 aAumid
.emplace(portableAumid
);
322 nsresult
ToastNotification::BackgroundDispatch(nsIRunnable
* runnable
) {
323 return NS_DispatchBackgroundTask(runnable
);
327 ToastNotification::GetSuppressForScreenSharing(bool* aRetVal
) {
328 *aRetVal
= mSuppressForScreenSharing
;
333 ToastNotification::SetSuppressForScreenSharing(bool aSuppress
) {
334 mSuppressForScreenSharing
= aSuppress
;
339 ToastNotification::Observe(nsISupports
* aSubject
, const char* aTopic
,
340 const char16_t
* aData
) {
341 nsDependentCString
topic(aTopic
);
343 for (auto iter
= mActiveHandlers
.Iter(); !iter
.Done(); iter
.Next()) {
344 RefPtr
<ToastNotificationHandler
> handler
= iter
.UserData();
346 auto removeNotification
= [&]() {
347 // The handlers' destructors will do the right thing (de-register with
351 // Break the cycle between the handler and the MSCOM notification so the
352 // handler's destructor will be called.
353 handler
->UnregisterHandler();
356 if (topic
== "last-pb-context-exited"_ns
) {
357 if (handler
->IsPrivate()) {
358 handler
->HideAlert();
359 removeNotification();
361 } else if (topic
== "quit-application"_ns
) {
362 removeNotification();
370 ToastNotification::ShowAlertNotification(
371 const nsAString
& aImageUrl
, const nsAString
& aAlertTitle
,
372 const nsAString
& aAlertText
, bool aAlertTextClickable
,
373 const nsAString
& aAlertCookie
, nsIObserver
* aAlertListener
,
374 const nsAString
& aAlertName
, const nsAString
& aBidi
, const nsAString
& aLang
,
375 const nsAString
& aData
, nsIPrincipal
* aPrincipal
, bool aInPrivateBrowsing
,
376 bool aRequireInteraction
) {
377 nsCOMPtr
<nsIAlertNotification
> alert
=
378 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID
);
379 if (NS_WARN_IF(!alert
)) {
380 return NS_ERROR_FAILURE
;
382 // vibrate is unused for now
383 nsTArray
<uint32_t> vibrate
;
384 nsresult rv
= alert
->Init(aAlertName
, aImageUrl
, aAlertTitle
, aAlertText
,
385 aAlertTextClickable
, aAlertCookie
, aBidi
, aLang
,
386 aData
, aPrincipal
, aInPrivateBrowsing
,
387 aRequireInteraction
, false, vibrate
);
388 if (NS_WARN_IF(NS_FAILED(rv
))) {
391 return ShowAlert(alert
, aAlertListener
);
395 ToastNotification::ShowPersistentNotification(const nsAString
& aPersistentData
,
396 nsIAlertNotification
* aAlert
,
397 nsIObserver
* aAlertListener
) {
398 return ShowAlert(aAlert
, aAlertListener
);
402 ToastNotification::SetManualDoNotDisturb(bool aDoNotDisturb
) {
403 return NS_ERROR_NOT_IMPLEMENTED
;
407 ToastNotification::GetManualDoNotDisturb(bool* aRet
) {
408 return NS_ERROR_NOT_IMPLEMENTED
;
412 ToastNotification::ShowAlert(nsIAlertNotification
* aAlert
,
413 nsIObserver
* aAlertListener
) {
414 NS_ENSURE_ARG(aAlert
);
416 if (mSuppressForScreenSharing
) {
421 MOZ_TRY(aAlert
->GetCookie(cookie
));
424 MOZ_TRY(aAlert
->GetName(name
));
427 MOZ_TRY(aAlert
->GetTitle(title
));
428 if (!EnsureUTF16Validity(title
)) {
429 MOZ_LOG(sWASLog
, LogLevel::Warning
,
430 ("Notification title was invalid UTF16, unpaired surrogates have "
435 MOZ_TRY(aAlert
->GetText(text
));
436 if (!EnsureUTF16Validity(text
)) {
437 MOZ_LOG(sWASLog
, LogLevel::Warning
,
438 ("Notification text was invalid UTF16, unpaired surrogates have "
443 MOZ_TRY(aAlert
->GetTextClickable(&textClickable
));
446 MOZ_TRY(aAlert
->GetSilent(&isSilent
));
448 nsAutoString hostPort
;
449 MOZ_TRY(aAlert
->GetSource(hostPort
));
451 nsAutoString opaqueRelaunchData
;
452 MOZ_TRY(aAlert
->GetOpaqueRelaunchData(opaqueRelaunchData
));
454 bool requireInteraction
;
455 MOZ_TRY(aAlert
->GetRequireInteraction(&requireInteraction
));
457 bool inPrivateBrowsing
;
458 MOZ_TRY(aAlert
->GetInPrivateBrowsing(&inPrivateBrowsing
));
460 nsTArray
<RefPtr
<nsIAlertAction
>> actions
;
461 MOZ_TRY(aAlert
->GetActions(actions
));
463 nsCOMPtr
<nsIPrincipal
> principal
;
464 MOZ_TRY(aAlert
->GetPrincipal(getter_AddRefs(principal
)));
465 bool isSystemPrincipal
= principal
&& principal
->IsSystemPrincipal();
467 bool handleActions
= false;
468 auto imagePlacement
= ImagePlacement::eInline
;
469 if (isSystemPrincipal
) {
470 nsCOMPtr
<nsIWindowsAlertNotification
> winAlert(do_QueryInterface(aAlert
));
472 MOZ_TRY(winAlert
->GetHandleActions(&handleActions
));
474 nsIWindowsAlertNotification::ImagePlacement placement
;
475 MOZ_TRY(winAlert
->GetImagePlacement(&placement
));
477 case nsIWindowsAlertNotification::eHero
:
478 imagePlacement
= ImagePlacement::eHero
;
480 case nsIWindowsAlertNotification::eIcon
:
481 imagePlacement
= ImagePlacement::eIcon
;
483 case nsIWindowsAlertNotification::eInline
:
484 imagePlacement
= ImagePlacement::eInline
;
487 MOZ_LOG(sWASLog
, LogLevel::Error
,
488 ("Invalid image placement enum value: %hhu", placement
));
489 return NS_ERROR_UNEXPECTED
;
494 RefPtr
<ToastNotificationHandler
> oldHandler
= mActiveHandlers
.Get(name
);
496 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
497 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
498 this, mAumid
.ref(), aAlertListener
, name
, cookie
, title
, text
, hostPort
,
499 textClickable
, requireInteraction
, actions
, isSystemPrincipal
,
500 opaqueRelaunchData
, inPrivateBrowsing
, isSilent
, handleActions
,
502 mActiveHandlers
.InsertOrUpdate(name
, RefPtr
{handler
});
504 MOZ_LOG(sWASLog
, LogLevel::Debug
,
505 ("Adding handler '%s': [%p] (now %d handlers)",
506 NS_ConvertUTF16toUTF8(name
).get(), handler
.get(),
507 mActiveHandlers
.Count()));
509 nsresult rv
= handler
->InitAlertAsync(aAlert
);
510 if (NS_WARN_IF(NS_FAILED(rv
))) {
511 MOZ_LOG(sWASLog
, LogLevel::Debug
,
512 ("Failed to init alert, removing '%s'",
513 NS_ConvertUTF16toUTF8(name
).get()));
514 mActiveHandlers
.Remove(name
);
515 handler
->UnregisterHandler();
519 // If there was a previous handler with the same name then unregister it.
521 oldHandler
->UnregisterHandler();
528 ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification
* aAlert
,
529 const nsAString
& aWindowsTag
,
530 nsAString
& aString
) {
531 NS_ENSURE_ARG(aAlert
);
534 MOZ_TRY(aAlert
->GetCookie(cookie
));
537 MOZ_TRY(aAlert
->GetName(name
));
540 MOZ_TRY(aAlert
->GetTitle(title
));
543 MOZ_TRY(aAlert
->GetText(text
));
546 MOZ_TRY(aAlert
->GetTextClickable(&textClickable
));
549 MOZ_TRY(aAlert
->GetSilent(&isSilent
));
551 nsAutoString hostPort
;
552 MOZ_TRY(aAlert
->GetSource(hostPort
));
554 nsAutoString opaqueRelaunchData
;
555 MOZ_TRY(aAlert
->GetOpaqueRelaunchData(opaqueRelaunchData
));
557 bool requireInteraction
;
558 MOZ_TRY(aAlert
->GetRequireInteraction(&requireInteraction
));
560 bool inPrivateBrowsing
;
561 MOZ_TRY(aAlert
->GetInPrivateBrowsing(&inPrivateBrowsing
));
563 nsTArray
<RefPtr
<nsIAlertAction
>> actions
;
564 MOZ_TRY(aAlert
->GetActions(actions
));
566 nsCOMPtr
<nsIPrincipal
> principal
;
567 MOZ_TRY(aAlert
->GetPrincipal(getter_AddRefs(principal
)));
568 bool isSystemPrincipal
= principal
&& principal
->IsSystemPrincipal();
570 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
571 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
572 this, mAumid
.ref(), nullptr /* aAlertListener */, name
, cookie
, title
,
573 text
, hostPort
, textClickable
, requireInteraction
, actions
,
574 isSystemPrincipal
, opaqueRelaunchData
, inPrivateBrowsing
, isSilent
);
576 // Usually, this will be empty during testing, making test output
578 MOZ_TRY(handler
->SetWindowsTag(aWindowsTag
));
580 nsAutoString imageURL
;
581 MOZ_TRY(aAlert
->GetImageURL(imageURL
));
583 return handler
->CreateToastXmlString(imageURL
, aString
);
586 // Verifies that the tag recieved associates to a notification created during
587 // this application's session, or handles fallback behavior.
588 RefPtr
<ToastHandledPromise
> ToastNotification::VerifyTagPresentOrFallback(
589 const nsAString
& aWindowsTag
) {
590 MOZ_LOG(sWASLog
, LogLevel::Debug
,
591 ("Iterating %d handlers", mActiveHandlers
.Count()));
593 for (auto iter
= mActiveHandlers
.Iter(); !iter
.Done(); iter
.Next()) {
594 RefPtr
<ToastNotificationHandler
> handler
= iter
.UserData();
596 nsresult rv
= handler
->GetWindowsTag(tag
);
598 if (NS_SUCCEEDED(rv
)) {
599 MOZ_LOG(sWASLog
, LogLevel::Debug
,
600 ("Comparing external windowsTag '%s' to handled windowsTag '%s'",
601 NS_ConvertUTF16toUTF8(aWindowsTag
).get(),
602 NS_ConvertUTF16toUTF8(tag
).get()));
603 if (aWindowsTag
.Equals(tag
)) {
604 MOZ_LOG(sWASLog
, LogLevel::Debug
,
605 ("External windowsTag '%s' is handled by handler [%p]",
606 NS_ConvertUTF16toUTF8(aWindowsTag
).get(), handler
.get()));
607 return ToastHandledPromise::CreateAndResolve(true, __func__
);
610 MOZ_LOG(sWASLog
, LogLevel::Debug
,
611 ("Failed to get windowsTag for handler [%p]", handler
.get()));
615 // Fallback handling is required.
617 MOZ_LOG(sWASLog
, LogLevel::Debug
,
618 ("External windowsTag '%s' is not handled",
619 NS_ConvertUTF16toUTF8(aWindowsTag
).get()));
621 RefPtr
<ToastHandledPromise::Private
> fallbackPromise
=
622 new ToastHandledPromise::Private(__func__
);
624 // TODO: Bug 1806005 - At time of writing this function is called in a call
625 // stack containing `WndProc` callback on an STA thread. As a result attempts
626 // to create a `ToastNotificationManager` instance results an an
627 // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM
628 // interface and synchronize the COM interactions if notification fallback
629 // handling were no longer handled in a `WndProc` context.
630 NS_DispatchBackgroundTask(NS_NewRunnableFunction(
631 "VerifyTagPresentOrFallback fallback background task",
632 [fallbackPromise
]() { fallbackPromise
->Resolve(false, __func__
); }));
634 return fallbackPromise
;
637 // Send our window's PID to the notification server so that it can grant us
638 // `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID.
639 // Absense of PID which may occur when we are yet unable to retrieve the
640 // window during startup, which is not a problem in practice as new windows
641 // receive focus by default.
642 void ToastNotification::SignalComNotificationHandled(
643 const nsAString
& aWindowsTag
) {
646 nsCOMPtr
<nsIWindowMediator
> winMediator(
647 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
));
649 nsCOMPtr
<mozIDOMWindowProxy
> navWin
;
650 winMediator
->GetMostRecentWindow(u
"navigator:browser",
651 getter_AddRefs(navWin
));
653 nsCOMPtr
<nsIWidget
> widget
=
654 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin
));
656 HWND hwnd
= (HWND
)widget
->GetNativeData(NS_NATIVE_WINDOW
);
657 GetWindowThreadProcessId(hwnd
, &pid
);
659 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get widget"));
662 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get navWin"));
665 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get WinMediator"));
668 // Run pipe communication off the main thread to prevent UI jank from
669 // blocking. Nothing relies on the COM server's response or that it has
670 // responded at time of commit.
671 NS_DispatchBackgroundTask(
672 NS_NewRunnableFunction(
673 "SignalComNotificationHandled background task",
674 [pid
, aWindowsTag
= nsString
{aWindowsTag
}]() mutable {
675 std::wstring pipeName
= GetNotificationPipeName(aWindowsTag
.get());
678 pipe
.own(CreateFileW(pipeName
.c_str(), GENERIC_READ
| GENERIC_WRITE
,
679 0, nullptr, OPEN_EXISTING
,
680 FILE_FLAG_OVERLAPPED
, nullptr));
681 if (pipe
.get() == INVALID_HANDLE_VALUE
) {
682 MOZ_LOG(sWASLog
, LogLevel::Error
,
683 ("Unable to open notification server pipe."));
687 DWORD pipeFlags
= PIPE_READMODE_MESSAGE
;
688 if (!SetNamedPipeHandleState(pipe
.get(), &pipeFlags
, nullptr,
690 MOZ_LOG(sWASLog
, LogLevel::Error
,
691 ("Error setting pipe handle state, error %lu",
696 // Pass our window's PID to the COM server receive
697 // `SetForegroundWindow` permissions, and wait for a message
698 // acknowledging the permission has been granted.
699 ToastNotificationPidMessage in
{};
701 ToastNotificationPermissionMessage out
{};
702 auto transact
= [&](OVERLAPPED
& overlapped
) {
703 return TransactNamedPipe(pipe
.get(), &in
, sizeof(in
), &out
,
704 sizeof(out
), nullptr, &overlapped
);
707 SyncDoOverlappedIOWithTimeout(pipe
, sizeof(out
), transact
);
709 if (result
&& out
.setForegroundPermissionGranted
&& pid
!= 0) {
711 sWASLog
, LogLevel::Info
,
712 ("SetForegroundWindow permission granted to our window."));
714 MOZ_LOG(sWASLog
, LogLevel::Error
,
715 ("SetForegroundWindow permission not granted to our "
719 NS_DISPATCH_EVENT_MAY_BLOCK
);
723 ToastNotification::HandleWindowsTag(const nsAString
& aWindowsTag
,
724 JSContext
* aCx
, dom::Promise
** aPromise
) {
725 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
726 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD
);
729 RefPtr
<dom::Promise
> promise
=
730 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx
), rv
);
731 ENSURE_SUCCESS(rv
, rv
.StealNSResult());
733 this->VerifyTagPresentOrFallback(aWindowsTag
)
735 GetMainThreadSerialEventTarget(), __func__
,
736 [aWindowsTag
= nsString(aWindowsTag
),
737 promise
](const bool aTagWasHandled
) {
738 // We no longer need to query toast information from OS and can
739 // allow the COM server to proceed (toast information is lost once
740 // the COM server's `Activate` callback returns).
741 SignalComNotificationHandled(aWindowsTag
);
744 if (NS_WARN_IF(!js
.Init(promise
->GetGlobalObject()))) {
745 promise
->MaybeReject(NS_ERROR_FAILURE
);
749 // Resolve the DOM Promise with a JS object. Set properties if
750 // fallback handling is necessary.
752 JSContext
* cx
= js
.cx();
753 JS::Rooted
<JSObject
*> obj(cx
, JS_NewPlainObject(cx
));
755 JS::Rooted
<JS::Value
> attVal(cx
, JS::BooleanValue(aTagWasHandled
));
756 Unused
<< NS_WARN_IF(
757 !JS_SetProperty(cx
, obj
, "tagWasHandled", attVal
));
759 promise
->MaybeResolve(obj
);
761 [aWindowsTag
= nsString(aWindowsTag
), promise
]() {
762 // We no longer need to query toast information from OS and can
763 // allow the COM server to proceed (toast information is lost once
764 // the COM server's `Activate` callback returns).
765 SignalComNotificationHandled(aWindowsTag
);
767 promise
->MaybeReject(NS_ERROR_FAILURE
);
770 promise
.forget(aPromise
);
775 ToastNotification::CloseAlert(const nsAString
& aAlertName
,
776 bool aContextClosed
) {
777 RefPtr
<ToastNotificationHandler
> handler
;
778 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
782 if (!aContextClosed
|| handler
->IsPrivate()) {
783 // Hide the alert when not implicitly closed by tab/window closing or when
784 // notification originated from a private tab.
785 handler
->HideAlert();
788 mActiveHandlers
.Remove(aAlertName
);
789 handler
->UnregisterHandler();
794 bool ToastNotification::IsActiveHandler(const nsAString
& aAlertName
,
795 ToastNotificationHandler
* aHandler
) {
796 RefPtr
<ToastNotificationHandler
> handler
;
797 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
800 return handler
== aHandler
;
803 void ToastNotification::RemoveHandler(const nsAString
& aAlertName
,
804 ToastNotificationHandler
* aHandler
) {
805 // The alert may have been replaced; only remove it from the active
806 // handler's map if it's the same.
807 if (IsActiveHandler(aAlertName
, aHandler
)) {
808 // Terrible things happen if the destructor of a handler is called inside
809 // the hashtable .Remove() method. Wait until we have returned from there.
810 RefPtr
<ToastNotificationHandler
> kungFuDeathGrip(aHandler
);
811 mActiveHandlers
.Remove(aAlertName
);
812 aHandler
->UnregisterHandler();
817 ToastNotification::RemoveAllNotificationsForInstall() {
820 ComPtr
<IToastNotificationManagerStatics
> manager
;
821 hr
= GetActivationFactory(
823 RuntimeClass_Windows_UI_Notifications_ToastNotificationManager
)
826 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
829 MOZ_ASSERT(mAumid
.isSome());
830 hr
= aumid
.Set(mAumid
.ref().get());
831 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
833 // Hide toasts in action center.
835 ComPtr
<IToastNotificationManagerStatics2
> manager2
;
836 hr
= manager
.As(&manager2
);
837 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
839 ComPtr
<IToastNotificationHistory
> history
;
840 hr
= manager2
->get_History(&history
);
841 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
843 hr
= history
->ClearWithId(aumid
.Get());
844 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
847 // Hide scheduled toasts.
849 ComPtr
<IToastNotifier
> notifier
;
850 hr
= manager
->CreateToastNotifierWithId(aumid
.Get(), ¬ifier
);
851 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
853 ComPtr
<IVectorView_ScheduledToastNotification
> scheduledToasts
;
854 hr
= notifier
->GetScheduledToastNotifications(&scheduledToasts
);
855 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
857 unsigned int schedSize
;
858 hr
= scheduledToasts
->get_Size(&schedSize
);
859 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
861 for (unsigned int i
= 0; i
< schedSize
; i
++) {
862 ComPtr
<IScheduledToastNotification
> schedToast
;
863 hr
= scheduledToasts
->GetAt(i
, &schedToast
);
864 if (NS_WARN_IF(FAILED(hr
))) {
868 hr
= notifier
->RemoveFromSchedule(schedToast
.Get());
869 Unused
<< NS_WARN_IF(FAILED(hr
));
876 NS_IMPL_ISUPPORTS_INHERITED(WindowsAlertNotification
, AlertNotification
,
877 nsIWindowsAlertNotification
)
880 WindowsAlertNotification::GetHandleActions(bool* aHandleActions
) {
881 *aHandleActions
= mHandleActions
;
886 WindowsAlertNotification::SetHandleActions(bool aHandleActions
) {
887 mHandleActions
= aHandleActions
;
891 NS_IMETHODIMP
WindowsAlertNotification::GetImagePlacement(
892 nsIWindowsAlertNotification::ImagePlacement
* aImagePlacement
) {
893 *aImagePlacement
= mImagePlacement
;
897 NS_IMETHODIMP
WindowsAlertNotification::SetImagePlacement(
898 nsIWindowsAlertNotification::ImagePlacement aImagePlacement
) {
899 switch (aImagePlacement
) {
903 mImagePlacement
= aImagePlacement
;
906 MOZ_LOG(sWASLog
, LogLevel::Error
,
907 ("Invalid image placement enum value: %hhu", aImagePlacement
));
908 return NS_ERROR_INVALID_ARG
;
914 } // namespace widget
915 } // namespace mozilla