diff options
Diffstat (limited to 'toolkit/components/alerts/nsAlertsService.cpp')
-rw-r--r-- | toolkit/components/alerts/nsAlertsService.cpp | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/toolkit/components/alerts/nsAlertsService.cpp b/toolkit/components/alerts/nsAlertsService.cpp new file mode 100644 index 0000000000..9a8fb7e7dc --- /dev/null +++ b/toolkit/components/alerts/nsAlertsService.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_alerts.h" +#include "mozilla/Telemetry.h" +#include "nsXULAppAPI.h" + +#include "nsAlertsService.h" + +#include "nsXPCOM.h" +#include "nsPromiseFlatString.h" +#include "nsToolkitCompsCID.h" +#include "nsComponentManagerUtils.h" + +#ifdef MOZ_PLACES +# include "nsIFaviconService.h" +#endif // MOZ_PLACES + +#ifdef XP_WIN +# include <shellapi.h> +#endif + +using namespace mozilla; + +using mozilla::dom::ContentChild; + +namespace { + +#ifdef MOZ_PLACES + +class IconCallback final : public nsIFaviconDataCallback { + public: + NS_DECL_ISUPPORTS + + IconCallback(nsIAlertsService* aBackend, nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) + : mBackend(aBackend), mAlert(aAlert), mAlertListener(aAlertListener) {} + + NS_IMETHOD + OnComplete(nsIURI* aIconURI, uint32_t aIconSize, const uint8_t* aIconData, + const nsACString& aMimeType, uint16_t aWidth) override { + nsresult rv = NS_ERROR_FAILURE; + if (aIconSize > 0) { + nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend)); + if (alertsIconData) { + rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener, + aIconSize, aIconData); + } + } else if (aIconURI) { + nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend)); + if (alertsIconURI) { + rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener, + aIconURI); + } + } + if (NS_FAILED(rv)) { + rv = mBackend->ShowAlert(mAlert, mAlertListener); + } + return rv; + } + + private: + virtual ~IconCallback() = default; + + nsCOMPtr<nsIAlertsService> mBackend; + nsCOMPtr<nsIAlertNotification> mAlert; + nsCOMPtr<nsIObserver> mAlertListener; +}; + +NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback) + +#endif // MOZ_PLACES + +nsresult ShowWithIconBackend(nsIAlertsService* aBackend, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { +#ifdef MOZ_PLACES + nsCOMPtr<nsIURI> uri; + nsresult rv = aAlert->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv) || !uri) { + return NS_ERROR_FAILURE; + } + + // Ensure the backend supports favicons. + nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend)); + nsCOMPtr<nsIAlertsIconURI> alertsIconURI; + if (!alertsIconData) { + alertsIconURI = do_QueryInterface(aBackend); + } + if (!alertsIconData && !alertsIconURI) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr<nsIFaviconService> favicons( + do_GetService("@mozilla.org/browser/favicon-service;1")); + NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFaviconDataCallback> callback = + new IconCallback(aBackend, aAlert, aAlertListener); + if (alertsIconData) { + return favicons->GetFaviconDataForPage(uri, callback, 0); + } + return favicons->GetFaviconURLForPage(uri, callback, 0); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !MOZ_PLACES +} + +nsresult ShowWithBackend(nsIAlertsService* aBackend, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener, + const nsAString& aPersistentData) { + if (!aPersistentData.IsEmpty()) { + return aBackend->ShowPersistentNotification(aPersistentData, aAlert, + aAlertListener); + } + + if (Preferences::GetBool("alerts.showFavicons")) { + nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + + // If favicons are disabled, or the backend doesn't support them, show the + // alert without one. + return aBackend->ShowAlert(aAlert, aAlertListener); +} + +} // anonymous namespace + +NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb) + +nsAlertsService::nsAlertsService() : mBackend(nullptr) { + mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID); +} + +nsAlertsService::~nsAlertsService() = default; + +bool nsAlertsService::ShouldShowAlert() { + bool result = true; + +#ifdef XP_WIN + if (!xpc::IsInAutomation()) { + QUERY_USER_NOTIFICATION_STATE qstate; + if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) { + if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) { + result = false; + } + } + } +#endif + + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + if (alertsDND) { + bool suppressForScreenSharing = false; + nsresult rv = + alertsDND->GetSuppressForScreenSharing(&suppressForScreenSharing); + if (NS_SUCCEEDED(rv)) { + result &= !suppressForScreenSharing; + } + } + + return result; +} + +bool nsAlertsService::ShouldUseSystemBackend() { + if (!mBackend) { + return false; + } + return StaticPrefs::alerts_useSystemBackend(); +} + +NS_IMETHODIMP nsAlertsService::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<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + // vibrate is unused + nsTArray<uint32_t> vibrate; + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, + aAlertTextClickable, aAlertCookie, aBidi, aLang, + aData, aPrincipal, aInPrivateBrowsing, + aRequireInteraction, false, vibrate); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + return ShowPersistentNotification(u""_ns, aAlert, aAlertListener); +} + +static bool ShouldFallBackToXUL() { +#if defined(XP_WIN) || defined(XP_MACOSX) + // We know we always have system backend on Windows and macOS. Let's not + // permanently fall back to XUL just because of temporary failure. + return false; +#else + // The system may not have the notification library, we should fall back to + // XUL. + return true; +#endif +} + +NS_IMETHODIMP nsAlertsService::ShowPersistentNotification( + const nsAString& aPersistentData, nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + NS_ENSURE_ARG(aAlert); + + nsAutoString cookie; + nsresult rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + + if (aAlertListener) cpc->AddRemoteAlertObserver(cookie, aAlertListener); + + cpc->SendShowAlert(aAlert); + return NS_OK; + } + + // Check if there is an optional service that handles system-level + // notifications + if (ShouldUseSystemBackend()) { + rv = ShowWithBackend(mBackend, aAlert, aAlertListener, aPersistentData); + if (NS_SUCCEEDED(rv) || !ShouldFallBackToXUL()) { + return rv; + } + // If the system backend failed to show the alert, clear the backend and + // retry with XUL notifications. Future alerts will always use XUL. + mBackend = nullptr; + } + + if (!ShouldShowAlert()) { + // Do not display the alert. Instead call alertfinished and get out. + if (aAlertListener) + aAlertListener->Observe(nullptr, "alertfinished", cookie.get()); + return NS_OK; + } + + // Use XUL notifications as a fallback if above methods have failed. + nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance()); + NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE); + return ShowWithBackend(xulBackend, aAlert, aAlertListener, aPersistentData); +} + +NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName, + bool aContextClosed) { + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + cpc->SendCloseAlert(nsAutoString(aAlertName), aContextClosed); + return NS_OK; + } + + nsresult rv; + // Try the system notification service. + if (ShouldUseSystemBackend()) { + rv = mBackend->CloseAlert(aAlertName, aContextClosed); + if (NS_WARN_IF(NS_FAILED(rv)) && ShouldFallBackToXUL()) { + // If the system backend failed to close the alert, fall back to XUL for + // future alerts. + mBackend = nullptr; + } + } else { + nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance()); + NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE); + rv = xulBackend->CloseAlert(aAlertName, aContextClosed); + } + return rv; +} + +// nsIAlertsDoNotDisturb +NS_IMETHODIMP nsAlertsService::GetManualDoNotDisturb(bool* aRetVal) { +#ifdef MOZ_WIDGET_ANDROID + return NS_ERROR_NOT_IMPLEMENTED; +#else + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + return alertsDND->GetManualDoNotDisturb(aRetVal); +#endif +} + +NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) { +#ifdef MOZ_WIDGET_ANDROID + return NS_ERROR_NOT_IMPLEMENTED; +#else + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + + nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb); + if (NS_SUCCEEDED(rv)) { + Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1); + } + return rv; +#endif +} + +NS_IMETHODIMP nsAlertsService::GetSuppressForScreenSharing(bool* aRetVal) { +#ifdef MOZ_WIDGET_ANDROID + return NS_ERROR_NOT_IMPLEMENTED; +#else + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + return alertsDND->GetSuppressForScreenSharing(aRetVal); +#endif +} + +NS_IMETHODIMP nsAlertsService::SetSuppressForScreenSharing(bool aSuppress) { +#ifdef MOZ_WIDGET_ANDROID + return NS_ERROR_NOT_IMPLEMENTED; +#else + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + return alertsDND->SetSuppressForScreenSharing(aSuppress); +#endif +} + +already_AddRefed<nsIAlertsDoNotDisturb> nsAlertsService::GetDNDBackend() { + nsCOMPtr<nsIAlertsService> backend; + // Try the system notification service. + if (ShouldUseSystemBackend()) { + backend = mBackend; + } + if (!backend) { + backend = nsXULAlerts::GetInstance(); + } + + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(do_QueryInterface(backend)); + return alertsDND.forget(); +} |