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/Logging.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/WidgetUtils.h"
26 #include "nsAppRunner.h"
27 #include "nsComponentManagerUtils.h"
29 #include "nsIObserverService.h"
30 #include "nsIWindowMediator.h"
31 #include "nsPIDOMWindow.h"
33 #include "nsThreadUtils.h"
34 #include "nsWindowsHelpers.h"
35 #include "nsXREDirProvider.h"
37 #include "ToastNotificationHandler.h"
38 #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 (!WinUtils::HasRegistryKey(HKEY_CLASSES_ROOT
, nsisAumidPath
.get())) {
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 RefPtr
<ToastNotificationHandler
> oldHandler
= mActiveHandlers
.Get(name
);
469 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
470 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
471 this, mAumid
.ref(), aAlertListener
, name
, cookie
, title
, text
, hostPort
,
472 textClickable
, requireInteraction
, actions
, isSystemPrincipal
,
473 opaqueRelaunchData
, inPrivateBrowsing
, isSilent
);
474 mActiveHandlers
.InsertOrUpdate(name
, RefPtr
{handler
});
476 MOZ_LOG(sWASLog
, LogLevel::Debug
,
477 ("Adding handler '%s': [%p] (now %d handlers)",
478 NS_ConvertUTF16toUTF8(name
).get(), handler
.get(),
479 mActiveHandlers
.Count()));
481 nsresult rv
= handler
->InitAlertAsync(aAlert
);
482 if (NS_WARN_IF(NS_FAILED(rv
))) {
483 MOZ_LOG(sWASLog
, LogLevel::Debug
,
484 ("Failed to init alert, removing '%s'",
485 NS_ConvertUTF16toUTF8(name
).get()));
486 mActiveHandlers
.Remove(name
);
487 handler
->UnregisterHandler();
491 // If there was a previous handler with the same name then unregister it.
493 oldHandler
->UnregisterHandler();
500 ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification
* aAlert
,
501 const nsAString
& aWindowsTag
,
502 nsAString
& aString
) {
503 NS_ENSURE_ARG(aAlert
);
506 MOZ_TRY(aAlert
->GetCookie(cookie
));
509 MOZ_TRY(aAlert
->GetName(name
));
512 MOZ_TRY(aAlert
->GetTitle(title
));
515 MOZ_TRY(aAlert
->GetText(text
));
518 MOZ_TRY(aAlert
->GetTextClickable(&textClickable
));
521 MOZ_TRY(aAlert
->GetSilent(&isSilent
));
523 nsAutoString hostPort
;
524 MOZ_TRY(aAlert
->GetSource(hostPort
));
526 nsAutoString opaqueRelaunchData
;
527 MOZ_TRY(aAlert
->GetOpaqueRelaunchData(opaqueRelaunchData
));
529 bool requireInteraction
;
530 MOZ_TRY(aAlert
->GetRequireInteraction(&requireInteraction
));
532 bool inPrivateBrowsing
;
533 MOZ_TRY(aAlert
->GetInPrivateBrowsing(&inPrivateBrowsing
));
535 nsTArray
<RefPtr
<nsIAlertAction
>> actions
;
536 MOZ_TRY(aAlert
->GetActions(actions
));
538 nsCOMPtr
<nsIPrincipal
> principal
;
539 MOZ_TRY(aAlert
->GetPrincipal(getter_AddRefs(principal
)));
540 bool isSystemPrincipal
= principal
&& principal
->IsSystemPrincipal();
542 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
543 RefPtr
<ToastNotificationHandler
> handler
= new ToastNotificationHandler(
544 this, mAumid
.ref(), nullptr /* aAlertListener */, name
, cookie
, title
,
545 text
, hostPort
, textClickable
, requireInteraction
, actions
,
546 isSystemPrincipal
, opaqueRelaunchData
, inPrivateBrowsing
, isSilent
);
548 // Usually, this will be empty during testing, making test output
550 MOZ_TRY(handler
->SetWindowsTag(aWindowsTag
));
552 nsAutoString imageURL
;
553 MOZ_TRY(aAlert
->GetImageURL(imageURL
));
555 return handler
->CreateToastXmlString(imageURL
, aString
);
558 // Verifies that the tag recieved associates to a notification created during
559 // this application's session, or handles fallback behavior.
560 RefPtr
<ToastHandledPromise
> ToastNotification::VerifyTagPresentOrFallback(
561 const nsAString
& aWindowsTag
) {
562 MOZ_LOG(sWASLog
, LogLevel::Debug
,
563 ("Iterating %d handlers", mActiveHandlers
.Count()));
565 for (auto iter
= mActiveHandlers
.Iter(); !iter
.Done(); iter
.Next()) {
566 RefPtr
<ToastNotificationHandler
> handler
= iter
.UserData();
568 nsresult rv
= handler
->GetWindowsTag(tag
);
570 if (NS_SUCCEEDED(rv
)) {
571 MOZ_LOG(sWASLog
, LogLevel::Debug
,
572 ("Comparing external windowsTag '%s' to handled windowsTag '%s'",
573 NS_ConvertUTF16toUTF8(aWindowsTag
).get(),
574 NS_ConvertUTF16toUTF8(tag
).get()));
575 if (aWindowsTag
.Equals(tag
)) {
576 MOZ_LOG(sWASLog
, LogLevel::Debug
,
577 ("External windowsTag '%s' is handled by handler [%p]",
578 NS_ConvertUTF16toUTF8(aWindowsTag
).get(), handler
.get()));
579 return ToastHandledPromise::CreateAndResolve(true, __func__
);
582 MOZ_LOG(sWASLog
, LogLevel::Debug
,
583 ("Failed to get windowsTag for handler [%p]", handler
.get()));
587 // Fallback handling is required.
589 MOZ_LOG(sWASLog
, LogLevel::Debug
,
590 ("External windowsTag '%s' is not handled",
591 NS_ConvertUTF16toUTF8(aWindowsTag
).get()));
593 RefPtr
<ToastHandledPromise::Private
> fallbackPromise
=
594 new ToastHandledPromise::Private(__func__
);
596 // TODO: Bug 1806005 - At time of writing this function is called in a call
597 // stack containing `WndProc` callback on an STA thread. As a result attempts
598 // to create a `ToastNotificationManager` instance results an an
599 // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM
600 // interface and synchronize the COM interactions if notification fallback
601 // handling were no longer handled in a `WndProc` context.
602 NS_DispatchBackgroundTask(NS_NewRunnableFunction(
603 "VerifyTagPresentOrFallback fallback background task",
604 [fallbackPromise
]() { fallbackPromise
->Resolve(false, __func__
); }));
606 return fallbackPromise
;
609 // Send our window's PID to the notification server so that it can grant us
610 // `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID.
611 // Absense of PID which may occur when we are yet unable to retrieve the
612 // window during startup, which is not a problem in practice as new windows
613 // receive focus by default.
614 void ToastNotification::SignalComNotificationHandled(
615 const nsAString
& aWindowsTag
) {
618 nsCOMPtr
<nsIWindowMediator
> winMediator(
619 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
));
621 nsCOMPtr
<mozIDOMWindowProxy
> navWin
;
622 winMediator
->GetMostRecentWindow(u
"navigator:browser",
623 getter_AddRefs(navWin
));
625 nsCOMPtr
<nsIWidget
> widget
=
626 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin
));
628 HWND hwnd
= (HWND
)widget
->GetNativeData(NS_NATIVE_WINDOW
);
629 GetWindowThreadProcessId(hwnd
, &pid
);
631 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get widget"));
634 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get navWin"));
637 MOZ_LOG(sWASLog
, LogLevel::Debug
, ("Failed to get WinMediator"));
640 // Run pipe communication off the main thread to prevent UI jank from
641 // blocking. Nothing relies on the COM server's response or that it has
642 // responded at time of commit.
643 NS_DispatchBackgroundTask(
644 NS_NewRunnableFunction(
645 "SignalComNotificationHandled background task",
646 [pid
, aWindowsTag
= nsString
{aWindowsTag
}]() mutable {
647 std::wstring pipeName
= GetNotificationPipeName(aWindowsTag
.get());
650 pipe
.own(CreateFileW(pipeName
.c_str(), GENERIC_READ
| GENERIC_WRITE
,
651 0, nullptr, OPEN_EXISTING
,
652 FILE_FLAG_OVERLAPPED
, nullptr));
653 if (pipe
.get() == INVALID_HANDLE_VALUE
) {
654 MOZ_LOG(sWASLog
, LogLevel::Error
,
655 ("Unable to open notification server pipe."));
659 DWORD pipeFlags
= PIPE_READMODE_MESSAGE
;
660 if (!SetNamedPipeHandleState(pipe
.get(), &pipeFlags
, nullptr,
662 MOZ_LOG(sWASLog
, LogLevel::Error
,
663 ("Error setting pipe handle state, error %lu",
668 // Pass our window's PID to the COM server receive
669 // `SetForegroundWindow` permissions, and wait for a message
670 // acknowledging the permission has been granted.
671 ToastNotificationPidMessage in
{};
673 ToastNotificationPermissionMessage out
{};
674 auto transact
= [&](OVERLAPPED
& overlapped
) {
675 return TransactNamedPipe(pipe
.get(), &in
, sizeof(in
), &out
,
676 sizeof(out
), nullptr, &overlapped
);
679 SyncDoOverlappedIOWithTimeout(pipe
, sizeof(out
), transact
);
681 if (result
&& out
.setForegroundPermissionGranted
&& pid
!= 0) {
683 sWASLog
, LogLevel::Info
,
684 ("SetForegroundWindow permission granted to our window."));
686 MOZ_LOG(sWASLog
, LogLevel::Error
,
687 ("SetForegroundWindow permission not granted to our "
691 NS_DISPATCH_EVENT_MAY_BLOCK
);
695 ToastNotification::HandleWindowsTag(const nsAString
& aWindowsTag
,
696 JSContext
* aCx
, dom::Promise
** aPromise
) {
697 NS_ENSURE_TRUE(mAumid
.isSome(), NS_ERROR_UNEXPECTED
);
698 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD
);
701 RefPtr
<dom::Promise
> promise
=
702 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx
), rv
);
703 ENSURE_SUCCESS(rv
, rv
.StealNSResult());
705 this->VerifyTagPresentOrFallback(aWindowsTag
)
707 GetMainThreadSerialEventTarget(), __func__
,
708 [aWindowsTag
= nsString(aWindowsTag
),
709 promise
](const bool aTagWasHandled
) {
710 // We no longer need to query toast information from OS and can
711 // allow the COM server to proceed (toast information is lost once
712 // the COM server's `Activate` callback returns).
713 SignalComNotificationHandled(aWindowsTag
);
716 if (NS_WARN_IF(!js
.Init(promise
->GetGlobalObject()))) {
717 promise
->MaybeReject(NS_ERROR_FAILURE
);
721 // Resolve the DOM Promise with a JS object. Set properties if
722 // fallback handling is necessary.
724 JSContext
* cx
= js
.cx();
725 JS::Rooted
<JSObject
*> obj(cx
, JS_NewPlainObject(cx
));
727 JS::Rooted
<JS::Value
> attVal(cx
, JS::BooleanValue(aTagWasHandled
));
728 Unused
<< NS_WARN_IF(
729 !JS_SetProperty(cx
, obj
, "tagWasHandled", attVal
));
731 promise
->MaybeResolve(obj
);
733 [aWindowsTag
= nsString(aWindowsTag
), promise
]() {
734 // We no longer need to query toast information from OS and can
735 // allow the COM server to proceed (toast information is lost once
736 // the COM server's `Activate` callback returns).
737 SignalComNotificationHandled(aWindowsTag
);
739 promise
->MaybeReject(NS_ERROR_FAILURE
);
742 promise
.forget(aPromise
);
747 ToastNotification::CloseAlert(const nsAString
& aAlertName
,
748 bool aContextClosed
) {
749 RefPtr
<ToastNotificationHandler
> handler
;
750 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
754 if (!aContextClosed
|| handler
->IsPrivate()) {
755 // Hide the alert when not implicitly closed by tab/window closing or when
756 // notification originated from a private tab.
757 handler
->HideAlert();
760 mActiveHandlers
.Remove(aAlertName
);
761 handler
->UnregisterHandler();
766 bool ToastNotification::IsActiveHandler(const nsAString
& aAlertName
,
767 ToastNotificationHandler
* aHandler
) {
768 RefPtr
<ToastNotificationHandler
> handler
;
769 if (NS_WARN_IF(!mActiveHandlers
.Get(aAlertName
, getter_AddRefs(handler
)))) {
772 return handler
== aHandler
;
775 void ToastNotification::RemoveHandler(const nsAString
& aAlertName
,
776 ToastNotificationHandler
* aHandler
) {
777 // The alert may have been replaced; only remove it from the active
778 // handler's map if it's the same.
779 if (IsActiveHandler(aAlertName
, aHandler
)) {
780 // Terrible things happen if the destructor of a handler is called inside
781 // the hashtable .Remove() method. Wait until we have returned from there.
782 RefPtr
<ToastNotificationHandler
> kungFuDeathGrip(aHandler
);
783 mActiveHandlers
.Remove(aAlertName
);
784 aHandler
->UnregisterHandler();
789 ToastNotification::RemoveAllNotificationsForInstall() {
792 ComPtr
<IToastNotificationManagerStatics
> manager
;
793 hr
= GetActivationFactory(
795 RuntimeClass_Windows_UI_Notifications_ToastNotificationManager
)
798 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
801 MOZ_ASSERT(mAumid
.isSome());
802 hr
= aumid
.Set(mAumid
.ref().get());
803 NS_ENSURE_TRUE(SUCCEEDED(hr
), NS_ERROR_FAILURE
);
805 // Hide toasts in action center.
807 ComPtr
<IToastNotificationManagerStatics2
> manager2
;
808 hr
= manager
.As(&manager2
);
809 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
811 ComPtr
<IToastNotificationHistory
> history
;
812 hr
= manager2
->get_History(&history
);
813 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
815 hr
= history
->ClearWithId(aumid
.Get());
816 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
819 // Hide scheduled toasts.
821 ComPtr
<IToastNotifier
> notifier
;
822 hr
= manager
->CreateToastNotifierWithId(aumid
.Get(), ¬ifier
);
823 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
825 ComPtr
<IVectorView_ScheduledToastNotification
> scheduledToasts
;
826 hr
= notifier
->GetScheduledToastNotifications(&scheduledToasts
);
827 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
829 unsigned int schedSize
;
830 hr
= scheduledToasts
->get_Size(&schedSize
);
831 NS_ENSURE_TRUE_VOID(SUCCEEDED(hr
));
833 for (unsigned int i
= 0; i
< schedSize
; i
++) {
834 ComPtr
<IScheduledToastNotification
> schedToast
;
835 hr
= scheduledToasts
->GetAt(i
, &schedToast
);
836 if (NS_WARN_IF(FAILED(hr
))) {
840 hr
= notifier
->RemoveFromSchedule(schedToast
.Get());
841 Unused
<< NS_WARN_IF(FAILED(hr
));
848 } // namespace widget
849 } // namespace mozilla