/* -*- 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 "mozilla/WindowsVersion.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsIObserverService.h" #include "nsString.h" #include "nsThreadUtils.h" #include "ToastNotificationHandler.h" #include "WinTaskbar.h" namespace mozilla { namespace widget { NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIObserver, nsISupportsWeakReference) ToastNotification::ToastNotification() = default; ToastNotification::~ToastNotification() = default; nsresult ToastNotification::Init() { if (!IsWin8OrLater()) { return NS_ERROR_NOT_IMPLEMENTED; } nsAutoString uid; if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) { // Windows Toast Notification requires AppId return NS_ERROR_NOT_IMPLEMENTED; } nsresult rv = NS_NewNamedThread("ToastBgThread", getter_AddRefs(mBackgroundThread)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr obsServ = do_GetService("@mozilla.org/observer-service;1"); if (obsServ) { obsServ->AddObserver(this, "quit-application", true); } return NS_OK; } nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) { return mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL); } NS_IMETHODIMP ToastNotification::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { // Got quit-application // The handlers destructors will do the right thing (de-register with // Windows). for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) { RefPtr handler = iter.UserData(); iter.Remove(); // Break the cycle between the handler and the MSCOM notification so the // handler's destructor will be called. handler->UnregisterHandler(); } 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; } nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, aAlertCookie, aBidi, aLang, aData, aPrincipal, aInPrivateBrowsing, aRequireInteraction); 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::ShowAlert(nsIAlertNotification* aAlert, nsIObserver* aAlertListener) { if (NS_WARN_IF(!aAlert)) { return NS_ERROR_INVALID_ARG; } nsAutoString cookie; nsresult rv = aAlert->GetCookie(cookie); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoString name; rv = aAlert->GetName(name); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoString title; rv = aAlert->GetTitle(title); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoString text; rv = aAlert->GetText(text); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool textClickable; rv = aAlert->GetTextClickable(&textClickable); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoString hostPort; rv = aAlert->GetSource(hostPort); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr oldHandler = mActiveHandlers.Get(name); RefPtr handler = new ToastNotificationHandler( this, aAlertListener, name, cookie, title, text, hostPort, textClickable); mActiveHandlers.Put(name, RefPtr{handler}); rv = handler->InitAlertAsync(aAlert); if (NS_WARN_IF(NS_FAILED(rv))) { 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::CloseAlert(const nsAString& aAlertName) { RefPtr handler; if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { return NS_OK; } 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 // handlers 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(); } } } // namespace widget } // namespace mozilla