Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / widget / windows / ToastNotification.cpp
blobafdffc19acbeb8a561adb50e960f5f3b2b44d5a2
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"
9 #include <windows.h>
10 #include <appmodel.h>
11 #include <ktmw32.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"
29 #include "nsCOMPtr.h"
30 #include "nsIObserverService.h"
31 #include "nsIWindowMediator.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsString.h"
34 #include "nsThreadUtils.h"
35 #include "nsWindowsHelpers.h"
36 #include "nsXREDirProvider.h"
37 #include "prenv.h"
38 #include "ToastNotificationHandler.h"
39 #include "ToastNotificationHeaderOnlyUtils.h"
41 namespace mozilla {
42 namespace widget {
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;
75 } else {
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();
85 if (obsServ) {
86 Unused << NS_WARN_IF(
87 NS_FAILED(obsServ->AddObserver(this, "last-pb-context-exited", false)));
88 Unused << NS_WARN_IF(
89 NS_FAILED(obsServ->AddObserver(this, "quit-application", false)));
92 return NS_OK;
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)) {
99 MOZ_LOG(
100 sWASLog, LogLevel::Info,
101 ("Found MSIX AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get()));
102 return true;
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()));
115 return true;
118 // No AUMID registered, fall through to runtime registration for development
119 // and portable builds.
120 if (RegisterRuntimeAumid(installHash, mAumid)) {
121 MOZ_LOG(
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()));
126 return true;
129 MOZ_LOG(sWASLog, LogLevel::Warning,
130 ("Failed to register AUMID at runtime! (for install hash '%s')",
131 NS_ConvertUTF16toUTF8(installHash).get()));
132 return false;
135 bool ToastNotification::AssignIfMsixAumid(Maybe<nsAutoString>& aAumid) {
136 UINT32 len = 0;
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"));
142 return false;
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());
149 return true;
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()));
161 return false;
164 aAumid.emplace(nsisAumidName);
165 return true;
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);
207 nsAutoHandle txn;
208 // Manipulate the registry using a transaction so that any failures are
209 // rolled back.
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);
215 LSTATUS status;
217 auto RegisterKey = [&](const nsAString& path, nsAutoRegKey& key) {
218 HKEY rawKey;
219 status = ::RegCreateKeyTransactedW(
220 HKEY_CURRENT_USER, PromiseFlatString(path).get(), 0, nullptr,
221 REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, nullptr, txn,
222 nullptr);
223 NS_ENSURE_TRUE(status == ERROR_SUCCESS, false);
225 key.own(rawKey);
226 return true;
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;
238 // clang-format off
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}
247 // clang-format on
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(),
261 &bufferSizeBytes);
263 CLSID unused;
264 if (status == ERROR_SUCCESS &&
265 SUCCEEDED(CLSIDFromString(guidBuffer.Elements(), &unused))) {
266 guidStr = guidBuffer.Elements();
267 } else {
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()));
278 } else {
279 MOZ_LOG(sWASLog, LogLevel::Debug,
280 ("New CustomActivator guid generated: '%s'",
281 NS_ConvertUTF16toUTF8(guidStr).get()));
284 NS_ENSURE_TRUE(
285 RegisterValue(aumidKey, u"CustomActivator"_ns, REG_SZ, guidStr), false);
286 nsAutoString brandName;
287 WidgetUtils::GetBrandShortName(brandName);
288 NS_ENSURE_TRUE(
289 RegisterValue(aumidKey, u"DisplayName"_ns, REG_EXPAND_SZ, brandName),
290 false);
291 NS_ENSURE_TRUE(
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),
298 false);
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);
313 MOZ_LOG(
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);
319 return true;
322 nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) {
323 return NS_DispatchBackgroundTask(runnable);
326 NS_IMETHODIMP
327 ToastNotification::GetSuppressForScreenSharing(bool* aRetVal) {
328 *aRetVal = mSuppressForScreenSharing;
329 return NS_OK;
332 NS_IMETHODIMP
333 ToastNotification::SetSuppressForScreenSharing(bool aSuppress) {
334 mSuppressForScreenSharing = aSuppress;
335 return NS_OK;
338 NS_IMETHODIMP
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
348 // Windows).
349 iter.Remove();
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();
366 return NS_OK;
369 NS_IMETHODIMP
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))) {
389 return rv;
391 return ShowAlert(alert, aAlertListener);
394 NS_IMETHODIMP
395 ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData,
396 nsIAlertNotification* aAlert,
397 nsIObserver* aAlertListener) {
398 return ShowAlert(aAlert, aAlertListener);
401 NS_IMETHODIMP
402 ToastNotification::SetManualDoNotDisturb(bool aDoNotDisturb) {
403 return NS_ERROR_NOT_IMPLEMENTED;
406 NS_IMETHODIMP
407 ToastNotification::GetManualDoNotDisturb(bool* aRet) {
408 return NS_ERROR_NOT_IMPLEMENTED;
411 NS_IMETHODIMP
412 ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
413 nsIObserver* aAlertListener) {
414 NS_ENSURE_ARG(aAlert);
416 if (mSuppressForScreenSharing) {
417 return NS_OK;
420 nsAutoString cookie;
421 MOZ_TRY(aAlert->GetCookie(cookie));
423 nsAutoString name;
424 MOZ_TRY(aAlert->GetName(name));
426 nsAutoString title;
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 "
431 "been replaced."));
434 nsAutoString text;
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 "
439 "been replaced."));
442 bool textClickable;
443 MOZ_TRY(aAlert->GetTextClickable(&textClickable));
445 bool isSilent;
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));
471 if (winAlert) {
472 MOZ_TRY(winAlert->GetHandleActions(&handleActions));
474 nsIWindowsAlertNotification::ImagePlacement placement;
475 MOZ_TRY(winAlert->GetImagePlacement(&placement));
476 switch (placement) {
477 case nsIWindowsAlertNotification::eHero:
478 imagePlacement = ImagePlacement::eHero;
479 break;
480 case nsIWindowsAlertNotification::eIcon:
481 imagePlacement = ImagePlacement::eIcon;
482 break;
483 case nsIWindowsAlertNotification::eInline:
484 imagePlacement = ImagePlacement::eInline;
485 break;
486 default:
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,
501 imagePlacement);
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();
516 return rv;
519 // If there was a previous handler with the same name then unregister it.
520 if (oldHandler) {
521 oldHandler->UnregisterHandler();
524 return NS_OK;
527 NS_IMETHODIMP
528 ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification* aAlert,
529 const nsAString& aWindowsTag,
530 nsAString& aString) {
531 NS_ENSURE_ARG(aAlert);
533 nsAutoString cookie;
534 MOZ_TRY(aAlert->GetCookie(cookie));
536 nsAutoString name;
537 MOZ_TRY(aAlert->GetName(name));
539 nsAutoString title;
540 MOZ_TRY(aAlert->GetTitle(title));
542 nsAutoString text;
543 MOZ_TRY(aAlert->GetText(text));
545 bool textClickable;
546 MOZ_TRY(aAlert->GetTextClickable(&textClickable));
548 bool isSilent;
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
577 // deterministic.
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();
595 nsAutoString tag;
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__);
609 } else {
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) {
644 DWORD pid = 0;
646 nsCOMPtr<nsIWindowMediator> winMediator(
647 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
648 if (winMediator) {
649 nsCOMPtr<mozIDOMWindowProxy> navWin;
650 winMediator->GetMostRecentWindow(u"navigator:browser",
651 getter_AddRefs(navWin));
652 if (navWin) {
653 nsCOMPtr<nsIWidget> widget =
654 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin));
655 if (widget) {
656 HWND hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
657 GetWindowThreadProcessId(hwnd, &pid);
658 } else {
659 MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get widget"));
661 } else {
662 MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get navWin"));
664 } else {
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());
677 nsAutoHandle pipe;
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."));
684 return;
687 DWORD pipeFlags = PIPE_READMODE_MESSAGE;
688 if (!SetNamedPipeHandleState(pipe.get(), &pipeFlags, nullptr,
689 nullptr)) {
690 MOZ_LOG(sWASLog, LogLevel::Error,
691 ("Error setting pipe handle state, error %lu",
692 GetLastError()));
693 return;
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{};
700 in.pid = pid;
701 ToastNotificationPermissionMessage out{};
702 auto transact = [&](OVERLAPPED& overlapped) {
703 return TransactNamedPipe(pipe.get(), &in, sizeof(in), &out,
704 sizeof(out), nullptr, &overlapped);
706 bool result =
707 SyncDoOverlappedIOWithTimeout(pipe, sizeof(out), transact);
709 if (result && out.setForegroundPermissionGranted && pid != 0) {
710 MOZ_LOG(
711 sWASLog, LogLevel::Info,
712 ("SetForegroundWindow permission granted to our window."));
713 } else {
714 MOZ_LOG(sWASLog, LogLevel::Error,
715 ("SetForegroundWindow permission not granted to our "
716 "window."));
719 NS_DISPATCH_EVENT_MAY_BLOCK);
722 NS_IMETHODIMP
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);
728 ErrorResult rv;
729 RefPtr<dom::Promise> promise =
730 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
731 ENSURE_SUCCESS(rv, rv.StealNSResult());
733 this->VerifyTagPresentOrFallback(aWindowsTag)
734 ->Then(
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);
743 dom::AutoJSAPI js;
744 if (NS_WARN_IF(!js.Init(promise->GetGlobalObject()))) {
745 promise->MaybeReject(NS_ERROR_FAILURE);
746 return;
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);
771 return NS_OK;
774 NS_IMETHODIMP
775 ToastNotification::CloseAlert(const nsAString& aAlertName,
776 bool aContextClosed) {
777 RefPtr<ToastNotificationHandler> handler;
778 if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
779 return NS_OK;
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();
791 return NS_OK;
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)))) {
798 return false;
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();
816 NS_IMETHODIMP
817 ToastNotification::RemoveAllNotificationsForInstall() {
818 HRESULT hr = S_OK;
820 ComPtr<IToastNotificationManagerStatics> manager;
821 hr = GetActivationFactory(
822 HStringReference(
823 RuntimeClass_Windows_UI_Notifications_ToastNotificationManager)
824 .Get(),
825 &manager);
826 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
828 HString aumid;
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.
834 [&]() {
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));
845 }();
847 // Hide scheduled toasts.
848 [&]() {
849 ComPtr<IToastNotifier> notifier;
850 hr = manager->CreateToastNotifierWithId(aumid.Get(), &notifier);
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))) {
865 continue;
868 hr = notifier->RemoveFromSchedule(schedToast.Get());
869 Unused << NS_WARN_IF(FAILED(hr));
871 }();
873 return NS_OK;
876 NS_IMPL_ISUPPORTS_INHERITED(WindowsAlertNotification, AlertNotification,
877 nsIWindowsAlertNotification)
879 NS_IMETHODIMP
880 WindowsAlertNotification::GetHandleActions(bool* aHandleActions) {
881 *aHandleActions = mHandleActions;
882 return NS_OK;
885 NS_IMETHODIMP
886 WindowsAlertNotification::SetHandleActions(bool aHandleActions) {
887 mHandleActions = aHandleActions;
888 return NS_OK;
891 NS_IMETHODIMP WindowsAlertNotification::GetImagePlacement(
892 nsIWindowsAlertNotification::ImagePlacement* aImagePlacement) {
893 *aImagePlacement = mImagePlacement;
894 return NS_OK;
897 NS_IMETHODIMP WindowsAlertNotification::SetImagePlacement(
898 nsIWindowsAlertNotification::ImagePlacement aImagePlacement) {
899 switch (aImagePlacement) {
900 case eHero:
901 case eIcon:
902 case eInline:
903 mImagePlacement = aImagePlacement;
904 break;
905 default:
906 MOZ_LOG(sWASLog, LogLevel::Error,
907 ("Invalid image placement enum value: %hhu", aImagePlacement));
908 return NS_ERROR_INVALID_ARG;
911 return NS_OK;
914 } // namespace widget
915 } // namespace mozilla