/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sts=2 sw=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ToastNotification.h" #include #include #include #include #include #include "ErrorList.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Buffer.h" #include "mozilla/dom/Promise.h" #include "mozilla/DynamicallyLinkedFunctionPtr.h" #include "mozilla/ErrorResult.h" #include "mozilla/mscom/COMWrappers.h" #include "mozilla/mscom/Utils.h" #include "mozilla/Logging.h" #include "mozilla/Services.h" #include "mozilla/WidgetUtils.h" #include "mozilla/WindowsVersion.h" #include "nsAppRunner.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsIObserverService.h" #include "nsIWindowMediator.h" #include "nsPIDOMWindow.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsWindowsHelpers.h" #include "nsXREDirProvider.h" #include "prenv.h" #include "ToastNotificationHandler.h" #include "ToastNotificationHeaderOnlyUtils.h" #include "WinTaskbar.h" #include "WinUtils.h" namespace mozilla { namespace widget { using namespace toastnotification; using namespace ABI::Windows::Foundation; using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; // Needed to disambiguate internal and Windows `ToastNotification` classes. using namespace ABI::Windows::UI::Notifications; using WinToastNotification = ABI::Windows::UI::Notifications::ToastNotification; using IVectorView_ToastNotification = ABI::Windows::Foundation::Collections::IVectorView; using IVectorView_ScheduledToastNotification = ABI::Windows::Foundation::Collections::IVectorView< ScheduledToastNotification*>; LazyLogModule sWASLog("WindowsAlertsService"); NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIWindowsAlertsService, nsIAlertsDoNotDisturb, nsIObserver) ToastNotification::ToastNotification() = default; ToastNotification::~ToastNotification() = default; nsresult ToastNotification::Init() { if (!IsWin8OrLater()) { return NS_ERROR_NOT_IMPLEMENTED; } if (!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { // Windows Toast Notification requires AppId. But allow `xpcshell` to // create the service to test other functionality. if (!EnsureAumidRegistered()) { MOZ_LOG(sWASLog, LogLevel::Warning, ("Failed to register AUMID!")); return NS_ERROR_NOT_IMPLEMENTED; } } else { MOZ_LOG(sWASLog, LogLevel::Info, ("Using dummy AUMID in xpcshell test")); mAumid.emplace(u"XpcshellTestToastAumid"_ns); } MOZ_LOG(sWASLog, LogLevel::Info, ("Using AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get())); nsCOMPtr obsServ = mozilla::services::GetObserverService(); if (obsServ) { Unused << NS_WARN_IF( NS_FAILED(obsServ->AddObserver(this, "last-pb-context-exited", false))); Unused << NS_WARN_IF( NS_FAILED(obsServ->AddObserver(this, "quit-application", false))); } return NS_OK; } bool ToastNotification::EnsureAumidRegistered() { // Check if this is an MSIX install, app identity is provided by the package // so no registration is necessary. if (AssignIfMsixAumid(mAumid)) { MOZ_LOG( sWASLog, LogLevel::Info, ("Found MSIX AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get())); return true; } // Fall back to start menu shortcut for Windows 8; toast AUMID registration in // the registry only works in Windows 10+. if (!IsWin10OrLater()) { nsAutoString aumid; if (!WinTaskbar::GetAppUserModelID(aumid)) { return false; } mAumid = Some(aumid); return true; } nsAutoString installHash; nsresult rv = gDirServiceProvider->GetInstallHash(installHash); NS_ENSURE_SUCCESS(rv, false); // Check if toasts were registered during NSIS/MSI installation. if (AssignIfNsisAumid(installHash, mAumid)) { MOZ_LOG(sWASLog, LogLevel::Info, ("Found AUMID from installer (with install hash '%s'): '%s'", NS_ConvertUTF16toUTF8(installHash).get(), NS_ConvertUTF16toUTF8(mAumid.ref()).get())); return true; } // No AUMID registered, fall through to runtime registration for development // and portable builds. if (RegisterRuntimeAumid(installHash, mAumid)) { MOZ_LOG( sWASLog, LogLevel::Info, ("Updated AUMID registration at runtime (for install hash '%s'): '%s'", NS_ConvertUTF16toUTF8(installHash).get(), NS_ConvertUTF16toUTF8(mAumid.ref()).get())); return true; } MOZ_LOG(sWASLog, LogLevel::Warning, ("Failed to register AUMID at runtime! (for install hash '%s')", NS_ConvertUTF16toUTF8(installHash).get())); return false; } bool ToastNotification::AssignIfMsixAumid(Maybe& aAumid) { // `GetCurrentApplicationUserModelId` added in Windows 8. DynamicallyLinkedFunctionPtr pGetCurrentApplicationUserModelId(L"kernel32.dll", "GetCurrentApplicationUserModelId"); if (!pGetCurrentApplicationUserModelId) { return false; } UINT32 len = 0; // ERROR_INSUFFICIENT_BUFFER signals that we're in an MSIX package, and // therefore should use the package's AUMID. if (pGetCurrentApplicationUserModelId(&len, nullptr) != ERROR_INSUFFICIENT_BUFFER) { MOZ_LOG(sWASLog, LogLevel::Debug, ("Not an MSIX package")); return false; } mozilla::Buffer buffer(len); LONG success = pGetCurrentApplicationUserModelId(&len, buffer.Elements()); NS_ENSURE_TRUE(success == ERROR_SUCCESS, false); aAumid.emplace(buffer.Elements()); return true; } bool ToastNotification::AssignIfNsisAumid(nsAutoString& aInstallHash, Maybe& aAumid) { nsAutoString nsisAumidName = u""_ns MOZ_TOAST_APP_NAME u"Toast-"_ns + aInstallHash; nsAutoString nsisAumidPath = u"AppUserModelId\\"_ns + nsisAumidName; if (!WinUtils::HasRegistryKey(HKEY_CLASSES_ROOT, nsisAumidPath.get())) { MOZ_LOG(sWASLog, LogLevel::Debug, ("No CustomActivator value from installer in key 'HKCR\\%s'", NS_ConvertUTF16toUTF8(nsisAumidPath).get())); return false; } aAumid.emplace(nsisAumidName); return true; } bool ToastNotification::RegisterRuntimeAumid(nsAutoString& aInstallHash, Maybe& aAumid) { // Portable AUMID slightly differs from installed AUMID so we can // differentiate installed to HKCU vs portable installs if necessary. nsAutoString portableAumid = u""_ns MOZ_TOAST_APP_NAME u"PortableToast-"_ns + aInstallHash; nsCOMPtr appdir; nsresult rv = gDirServiceProvider->GetGREDir()->Clone(getter_AddRefs(appdir)); NS_ENSURE_SUCCESS(rv, false); nsCOMPtr icon; rv = appdir->Clone(getter_AddRefs(icon)); NS_ENSURE_SUCCESS(rv, false); rv = icon->Append(u"browser"_ns); NS_ENSURE_SUCCESS(rv, false); rv = icon->Append(u"VisualElements"_ns); NS_ENSURE_SUCCESS(rv, false); rv = icon->Append(u"VisualElements_70.png"_ns); NS_ENSURE_SUCCESS(rv, false); nsAutoString iconPath; rv = icon->GetPath(iconPath); NS_ENSURE_SUCCESS(rv, false); nsCOMPtr comDll; rv = appdir->Clone(getter_AddRefs(comDll)); NS_ENSURE_SUCCESS(rv, false); rv = comDll->Append(u"notificationserver.dll"_ns); NS_ENSURE_SUCCESS(rv, false); nsAutoString dllPath; rv = comDll->GetPath(dllPath); NS_ENSURE_SUCCESS(rv, false); nsAutoHandle txn; // Manipulate the registry using a transaction so that any failures are // rolled back. wchar_t transactionName[] = L"" MOZ_TOAST_APP_NAME L" toast registration"; txn.own(::CreateTransaction(nullptr, nullptr, TRANSACTION_DO_NOT_PROMOTE, 0, 0, 0, transactionName)); NS_ENSURE_TRUE(txn.get() != INVALID_HANDLE_VALUE, false); LSTATUS status; auto RegisterKey = [&](const nsAString& path, nsAutoRegKey& key) { HKEY rawKey; status = ::RegCreateKeyTransactedW( HKEY_CURRENT_USER, PromiseFlatString(path).get(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, nullptr, txn, nullptr); NS_ENSURE_TRUE(status == ERROR_SUCCESS, false); key.own(rawKey); return true; }; auto RegisterValue = [&](nsAutoRegKey& key, const nsAString& name, unsigned long type, const nsAString& data) { status = ::RegSetValueExW( key, PromiseFlatString(name).get(), 0, type, static_cast(PromiseFlatString(data).get()), (data.Length() + 1) * sizeof(wchar_t)); return status == ERROR_SUCCESS; }; // clang-format off /* Writes the following keys and values to the registry. * HKEY_CURRENT_USER\Software\Classes\AppID\{GUID} DllSurrogate : REG_SZ = "" * \AppUserModelId\{MOZ_TOAST_APP_NAME}PortableToast-{install hash} CustomActivator : REG_SZ = {GUID} * DisplayName : REG_EXPAND_SZ = {display name} * IconUri : REG_EXPAND_SZ = {icon path} * \CLSID\{GUID} AppID : REG_SZ = {GUID} * \InprocServer32 (Default) : REG_SZ = {notificationserver.dll path} */ // clang-format on constexpr nsLiteralString classes = u"Software\\Classes\\"_ns; nsAutoString aumid = classes + u"AppUserModelId\\"_ns + portableAumid; nsAutoRegKey aumidKey; NS_ENSURE_TRUE(RegisterKey(aumid, aumidKey), false); nsAutoString guidStr; { DWORD bufferSizeBytes = NSID_LENGTH * sizeof(wchar_t); Buffer guidBuffer(bufferSizeBytes); status = ::RegGetValueW(HKEY_CURRENT_USER, aumid.get(), L"CustomActivator", RRF_RT_REG_SZ, 0, guidBuffer.Elements(), &bufferSizeBytes); CLSID unused; if (status == ERROR_SUCCESS && SUCCEEDED(CLSIDFromString(guidBuffer.Elements(), &unused))) { guidStr = guidBuffer.Elements(); } else { nsIDToCString uuidString(nsID::GenerateUUID()); size_t len = strlen(uuidString.get()); MOZ_ASSERT(len == NSID_LENGTH - 1); CopyASCIItoUTF16(nsDependentCSubstring(uuidString.get(), len), guidStr); } if (status == ERROR_SUCCESS) { MOZ_LOG(sWASLog, LogLevel::Debug, ("Existing CustomActivator guid found: '%s'", NS_ConvertUTF16toUTF8(guidStr).get())); } else { MOZ_LOG(sWASLog, LogLevel::Debug, ("New CustomActivator guid generated: '%s'", NS_ConvertUTF16toUTF8(guidStr).get())); } } NS_ENSURE_TRUE( RegisterValue(aumidKey, u"CustomActivator"_ns, REG_SZ, guidStr), false); nsAutoString brandName; WidgetUtils::GetBrandShortName(brandName); NS_ENSURE_TRUE( RegisterValue(aumidKey, u"DisplayName"_ns, REG_EXPAND_SZ, brandName), false); NS_ENSURE_TRUE( RegisterValue(aumidKey, u"IconUri"_ns, REG_EXPAND_SZ, iconPath), false); nsAutoString appid = classes + u"AppID\\"_ns + guidStr; nsAutoRegKey appidKey; NS_ENSURE_TRUE(RegisterKey(appid, appidKey), false); NS_ENSURE_TRUE(RegisterValue(appidKey, u"DllSurrogate"_ns, REG_SZ, u""_ns), false); nsAutoString clsid = classes + u"CLSID\\"_ns + guidStr; nsAutoRegKey clsidKey; NS_ENSURE_TRUE(RegisterKey(clsid, clsidKey), false); NS_ENSURE_TRUE(RegisterValue(clsidKey, u"AppID"_ns, REG_SZ, guidStr), false); nsAutoString inproc = clsid + u"\\InprocServer32"_ns; nsAutoRegKey inprocKey; NS_ENSURE_TRUE(RegisterKey(inproc, inprocKey), false); // Set the component's path to this DLL NS_ENSURE_TRUE(RegisterValue(inprocKey, u""_ns, REG_SZ, dllPath), false); NS_ENSURE_TRUE(::CommitTransaction(txn), false); MOZ_LOG( sWASLog, LogLevel::Debug, ("Updated registration for CustomActivator value in key 'HKCU\\%s': '%s'", NS_ConvertUTF16toUTF8(aumid).get(), NS_ConvertUTF16toUTF8(guidStr).get())); aAumid.emplace(portableAumid); return true; } nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) { return NS_DispatchBackgroundTask(runnable); } NS_IMETHODIMP ToastNotification::GetSuppressForScreenSharing(bool* aRetVal) { *aRetVal = mSuppressForScreenSharing; return NS_OK; } NS_IMETHODIMP ToastNotification::SetSuppressForScreenSharing(bool aSuppress) { mSuppressForScreenSharing = aSuppress; return NS_OK; } NS_IMETHODIMP ToastNotification::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { nsDependentCString topic(aTopic); for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) { RefPtr handler = iter.UserData(); auto removeNotification = [&]() { // The handlers' destructors will do the right thing (de-register with // Windows). iter.Remove(); // Break the cycle between the handler and the MSCOM notification so the // handler's destructor will be called. handler->UnregisterHandler(); }; if (topic == "last-pb-context-exited"_ns) { if (handler->IsPrivate()) { handler->HideAlert(); removeNotification(); } } else if (topic == "quit-application"_ns) { removeNotification(); } } return NS_OK; } NS_IMETHODIMP ToastNotification::ShowAlertNotification( const nsAString& aImageUrl, const nsAString& aAlertTitle, const nsAString& aAlertText, bool aAlertTextClickable, const nsAString& aAlertCookie, nsIObserver* aAlertListener, const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang, const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing, bool aRequireInteraction) { nsCOMPtr alert = do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); if (NS_WARN_IF(!alert)) { return NS_ERROR_FAILURE; } // vibrate is unused for now nsTArray vibrate; nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, aAlertCookie, aBidi, aLang, aData, aPrincipal, aInPrivateBrowsing, aRequireInteraction, false, vibrate); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return ShowAlert(alert, aAlertListener); } NS_IMETHODIMP ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData, nsIAlertNotification* aAlert, nsIObserver* aAlertListener) { return ShowAlert(aAlert, aAlertListener); } NS_IMETHODIMP ToastNotification::SetManualDoNotDisturb(bool aDoNotDisturb) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP ToastNotification::GetManualDoNotDisturb(bool* aRet) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP ToastNotification::ShowAlert(nsIAlertNotification* aAlert, nsIObserver* aAlertListener) { NS_ENSURE_ARG(aAlert); if (mSuppressForScreenSharing) { return NS_OK; } nsAutoString cookie; MOZ_TRY(aAlert->GetCookie(cookie)); nsAutoString name; MOZ_TRY(aAlert->GetName(name)); nsAutoString title; MOZ_TRY(aAlert->GetTitle(title)); if (!EnsureUTF16Validity(title)) { MOZ_LOG(sWASLog, LogLevel::Warning, ("Notification title was invalid UTF16, unpaired surrogates have " "been replaced.")); } nsAutoString text; MOZ_TRY(aAlert->GetText(text)); if (!EnsureUTF16Validity(text)) { MOZ_LOG(sWASLog, LogLevel::Warning, ("Notification text was invalid UTF16, unpaired surrogates have " "been replaced.")); } bool textClickable; MOZ_TRY(aAlert->GetTextClickable(&textClickable)); bool isSilent; MOZ_TRY(aAlert->GetSilent(&isSilent)); nsAutoString hostPort; MOZ_TRY(aAlert->GetSource(hostPort)); nsAutoString launchUrl; MOZ_TRY(aAlert->GetLaunchURL(launchUrl)); bool requireInteraction; MOZ_TRY(aAlert->GetRequireInteraction(&requireInteraction)); bool inPrivateBrowsing; MOZ_TRY(aAlert->GetInPrivateBrowsing(&inPrivateBrowsing)); nsTArray> actions; MOZ_TRY(aAlert->GetActions(actions)); nsCOMPtr principal; MOZ_TRY(aAlert->GetPrincipal(getter_AddRefs(principal))); bool isSystemPrincipal = principal && principal->IsSystemPrincipal(); RefPtr oldHandler = mActiveHandlers.Get(name); NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED); RefPtr handler = new ToastNotificationHandler( this, mAumid.ref(), aAlertListener, name, cookie, title, text, hostPort, textClickable, requireInteraction, actions, isSystemPrincipal, launchUrl, inPrivateBrowsing, isSilent); mActiveHandlers.InsertOrUpdate(name, RefPtr{handler}); MOZ_LOG(sWASLog, LogLevel::Debug, ("Adding handler '%s': [%p] (now %d handlers)", NS_ConvertUTF16toUTF8(name).get(), handler.get(), mActiveHandlers.Count())); nsresult rv = handler->InitAlertAsync(aAlert); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to init alert, removing '%s'", NS_ConvertUTF16toUTF8(name).get())); mActiveHandlers.Remove(name); handler->UnregisterHandler(); return rv; } // If there was a previous handler with the same name then unregister it. if (oldHandler) { oldHandler->UnregisterHandler(); } return NS_OK; } NS_IMETHODIMP ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification* aAlert, const nsAString& aWindowsTag, nsAString& aString) { NS_ENSURE_ARG(aAlert); nsAutoString cookie; MOZ_TRY(aAlert->GetCookie(cookie)); nsAutoString name; MOZ_TRY(aAlert->GetName(name)); nsAutoString title; MOZ_TRY(aAlert->GetTitle(title)); nsAutoString text; MOZ_TRY(aAlert->GetText(text)); bool textClickable; MOZ_TRY(aAlert->GetTextClickable(&textClickable)); bool isSilent; MOZ_TRY(aAlert->GetSilent(&isSilent)); nsAutoString hostPort; MOZ_TRY(aAlert->GetSource(hostPort)); nsAutoString launchUrl; MOZ_TRY(aAlert->GetLaunchURL(launchUrl)); bool requireInteraction; MOZ_TRY(aAlert->GetRequireInteraction(&requireInteraction)); bool inPrivateBrowsing; MOZ_TRY(aAlert->GetInPrivateBrowsing(&inPrivateBrowsing)); nsTArray> actions; MOZ_TRY(aAlert->GetActions(actions)); nsCOMPtr principal; MOZ_TRY(aAlert->GetPrincipal(getter_AddRefs(principal))); bool isSystemPrincipal = principal && principal->IsSystemPrincipal(); NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED); RefPtr handler = new ToastNotificationHandler( this, mAumid.ref(), nullptr /* aAlertListener */, name, cookie, title, text, hostPort, textClickable, requireInteraction, actions, isSystemPrincipal, launchUrl, inPrivateBrowsing, isSilent); // Usually, this will be empty during testing, making test output // deterministic. MOZ_TRY(handler->SetWindowsTag(aWindowsTag)); nsAutoString imageURL; MOZ_TRY(aAlert->GetImageURL(imageURL)); return handler->CreateToastXmlString(imageURL, aString); } // Verifies that the tag recieved associates to a notification created during // this application's session, or handles fallback behavior. RefPtr ToastNotification::VerifyTagPresentOrFallback( const nsAString& aWindowsTag) { MOZ_LOG(sWASLog, LogLevel::Debug, ("Iterating %d handlers", mActiveHandlers.Count())); for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) { RefPtr handler = iter.UserData(); nsAutoString tag; nsresult rv = handler->GetWindowsTag(tag); if (NS_SUCCEEDED(rv)) { MOZ_LOG(sWASLog, LogLevel::Debug, ("Comparing external windowsTag '%s' to handled windowsTag '%s'", NS_ConvertUTF16toUTF8(aWindowsTag).get(), NS_ConvertUTF16toUTF8(tag).get())); if (aWindowsTag.Equals(tag)) { MOZ_LOG(sWASLog, LogLevel::Debug, ("External windowsTag '%s' is handled by handler [%p]", NS_ConvertUTF16toUTF8(aWindowsTag).get(), handler.get())); ToastHandledResolve handled{u""_ns, u""_ns}; return ToastHandledPromise::CreateAndResolve(handled, __func__); } } else { MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get windowsTag for handler [%p]", handler.get())); } } // Fallback handling RefPtr fallbackPromise = new ToastHandledPromise::Private(__func__); // TODO: Bug 1806005 - At time of writing this function is called in a call // stack containing `WndProc` callback on an STA thread. As a result attempts // to create a `ToastNotificationManager` instance results an an // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM // interface and synchronize the COM interactions if notification fallback // handling were no longer handled in a `WndProc` context. NS_DispatchBackgroundTask(NS_NewRunnableFunction( "VerifyTagPresentOrFallback fallback background task", [fallbackPromise, aWindowsTag = nsString(aWindowsTag), aAumid = nsString(mAumid.ref())]() { MOZ_ASSERT(mscom::IsCOMInitializedOnCurrentThread()); bool foundTag; nsAutoString launchUrl; nsAutoString privilegedName; nsresult rv = ToastNotificationHandler:: FindLaunchURLAndPrivilegedNameForWindowsTag( aWindowsTag, aAumid, foundTag, launchUrl, privilegedName); if (NS_FAILED(rv) || !foundTag) { MOZ_LOG(sWASLog, LogLevel::Error, ("Failed to get launch URL and privileged name for " "notification tag '%s'", NS_ConvertUTF16toUTF8(aWindowsTag).get())); fallbackPromise->Reject(false, __func__); return; } MOZ_LOG(sWASLog, LogLevel::Debug, ("Found launch URL '%s' and privileged name '%s' for " "windowsTag '%s'", NS_ConvertUTF16toUTF8(launchUrl).get(), NS_ConvertUTF16toUTF8(privilegedName).get(), NS_ConvertUTF16toUTF8(aWindowsTag).get())); ToastHandledResolve handled{launchUrl, privilegedName}; fallbackPromise->Resolve(handled, __func__); })); return fallbackPromise; } // Send our window's PID to the notification server so that it can grant us // `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID. // Absense of PID which may occur when we are yet unable to retrieve the // window during startup, which is not a problem in practice as new windows // receive focus by default. void ToastNotification::SignalComNotificationHandled( const nsAString& aWindowsTag) { DWORD pid = 0; nsCOMPtr winMediator( do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); if (winMediator) { nsCOMPtr navWin; winMediator->GetMostRecentWindow(u"navigator:browser", getter_AddRefs(navWin)); if (navWin) { nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin)); if (widget) { HWND hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); GetWindowThreadProcessId(hwnd, &pid); } else { MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get widget")); } } else { MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get navWin")); } } else { MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get WinMediator")); } // Run pipe communication off the main thread to prevent UI jank from // blocking. Nothing relies on the COM server's response or that it has // responded at time of commit. NS_DispatchBackgroundTask( NS_NewRunnableFunction( "SignalComNotificationHandled background task", [pid, aWindowsTag = nsString{aWindowsTag}]() mutable { std::wstring pipeName = GetNotificationPipeName(aWindowsTag.get()); nsAutoHandle pipe; pipe.own(CreateFileW(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr)); if (pipe.get() == INVALID_HANDLE_VALUE) { MOZ_LOG(sWASLog, LogLevel::Error, ("Unable to open notification server pipe.")); return; } DWORD pipeFlags = PIPE_READMODE_MESSAGE; if (!SetNamedPipeHandleState(pipe.get(), &pipeFlags, nullptr, nullptr)) { MOZ_LOG(sWASLog, LogLevel::Error, ("Error setting pipe handle state, error %lu", GetLastError())); return; } // Pass our window's PID to the COM server receive // `SetForegroundWindow` permissions, and wait for a message // acknowledging the permission has been granted. ToastNotificationPidMessage in{}; in.pid = pid; ToastNotificationPermissionMessage out{}; auto transact = [&](OVERLAPPED& overlapped) { return TransactNamedPipe(pipe.get(), &in, sizeof(in), &out, sizeof(out), nullptr, &overlapped); }; bool result = SyncDoOverlappedIOWithTimeout(pipe, sizeof(out), transact); if (result && out.setForegroundPermissionGranted && pid != 0) { MOZ_LOG( sWASLog, LogLevel::Info, ("SetForegroundWindow permission granted to our window.")); } else { MOZ_LOG(sWASLog, LogLevel::Error, ("SetForegroundWindow permission not granted to our " "window.")); } }), NS_DISPATCH_EVENT_MAY_BLOCK); } NS_IMETHODIMP ToastNotification::HandleWindowsTag(const nsAString& aWindowsTag, JSContext* aCx, dom::Promise** aPromise) { NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); ErrorResult rv; RefPtr promise = dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); ENSURE_SUCCESS(rv, rv.StealNSResult()); this->VerifyTagPresentOrFallback(aWindowsTag) ->Then( GetMainThreadSerialEventTarget(), __func__, [aWindowsTag = nsString(aWindowsTag), promise](const ToastHandledResolve& aResolved) { // We no longer need to query toast information from OS and can // allow the COM server to proceed (toast information is lost once // the COM server's `Activate` callback returns). SignalComNotificationHandled(aWindowsTag); dom::AutoJSAPI js; if (NS_WARN_IF(!js.Init(promise->GetGlobalObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Resolve the DOM Promise with a JS object. Set `launchUrl` and/or // `privilegedName` properties if fallback handling is necessary. JSContext* cx = js.cx(); JS::Rooted obj(cx, JS_NewPlainObject(cx)); auto setProperty = [&](const char* name, const nsString& value) { JS::Rooted title(cx, JS_NewUCStringCopyZ(cx, value.get())); JS::Rooted attVal(cx, JS::StringValue(title)); Unused << NS_WARN_IF(!JS_SetProperty(cx, obj, name, attVal)); }; if (!aResolved.launchUrl.IsEmpty()) { setProperty("launchUrl", aResolved.launchUrl); } if (!aResolved.privilegedName.IsEmpty()) { setProperty("privilegedName", aResolved.privilegedName); } promise->MaybeResolve(obj); }, [aWindowsTag = nsString(aWindowsTag), promise]() { // We no longer need to query toast information from OS and can // allow the COM server to proceed (toast information is lost once // the COM server's `Activate` callback returns). SignalComNotificationHandled(aWindowsTag); promise->MaybeReject(NS_ERROR_FAILURE); }); promise.forget(aPromise); return NS_OK; } NS_IMETHODIMP ToastNotification::CloseAlert(const nsAString& aAlertName, bool aContextClosed) { RefPtr handler; if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { return NS_OK; } if (!aContextClosed || handler->IsPrivate()) { // Hide the alert when not implicitly closed by tab/window closing or when // notification originated from a private tab. handler->HideAlert(); } mActiveHandlers.Remove(aAlertName); handler->UnregisterHandler(); return NS_OK; } bool ToastNotification::IsActiveHandler(const nsAString& aAlertName, ToastNotificationHandler* aHandler) { RefPtr handler; if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { return false; } return handler == aHandler; } void ToastNotification::RemoveHandler(const nsAString& aAlertName, ToastNotificationHandler* aHandler) { // The alert may have been replaced; only remove it from the active // handler's map if it's the same. if (IsActiveHandler(aAlertName, aHandler)) { // Terrible things happen if the destructor of a handler is called inside // the hashtable .Remove() method. Wait until we have returned from there. RefPtr kungFuDeathGrip(aHandler); mActiveHandlers.Remove(aAlertName); aHandler->UnregisterHandler(); } } NS_IMETHODIMP ToastNotification::RemoveAllNotificationsForInstall() { HRESULT hr = S_OK; ComPtr manager; hr = GetActivationFactory( HStringReference( RuntimeClass_Windows_UI_Notifications_ToastNotificationManager) .Get(), &manager); NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); HString aumid; MOZ_ASSERT(mAumid.isSome()); hr = aumid.Set(mAumid.ref().get()); NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); // Hide toasts in action center. [&]() { ComPtr manager2; hr = manager.As(&manager2); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); ComPtr history; hr = manager2->get_History(&history); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); hr = history->ClearWithId(aumid.Get()); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); }(); // Hide scheduled toasts. [&]() { ComPtr notifier; hr = manager->CreateToastNotifierWithId(aumid.Get(), ¬ifier); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); ComPtr scheduledToasts; hr = notifier->GetScheduledToastNotifications(&scheduledToasts); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); unsigned int schedSize; hr = scheduledToasts->get_Size(&schedSize); NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); for (unsigned int i = 0; i < schedSize; i++) { ComPtr schedToast; hr = scheduledToasts->GetAt(i, &schedToast); if (NS_WARN_IF(FAILED(hr))) { continue; } hr = notifier->RemoveFromSchedule(schedToast.Get()); Unused << NS_WARN_IF(FAILED(hr)); } }(); return NS_OK; } } // namespace widget } // namespace mozilla