/* -*- 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 #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 alertsIconData(do_QueryInterface(mBackend)); if (alertsIconData) { rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener, aIconSize, aIconData); } } else if (aIconURI) { nsCOMPtr 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 mBackend; nsCOMPtr mAlert; nsCOMPtr mAlertListener; }; NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback) #endif // MOZ_PLACES nsresult ShowWithIconBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert, nsIObserver* aAlertListener) { #ifdef MOZ_PLACES nsCOMPtr uri; nsresult rv = aAlert->GetURI(getter_AddRefs(uri)); if (NS_FAILED(rv) || !uri) { return NS_ERROR_FAILURE; } // Ensure the backend supports favicons. nsCOMPtr alertsIconData(do_QueryInterface(aBackend)); nsCOMPtr alertsIconURI; if (!alertsIconData) { alertsIconURI = do_QueryInterface(aBackend); } if (!alertsIconData && !alertsIconURI) { return NS_ERROR_NOT_IMPLEMENTED; } nsCOMPtr favicons( do_GetService("@mozilla.org/browser/favicon-service;1")); NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE); nsCOMPtr 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 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 alert = do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); // vibrate is unused nsTArray 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 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 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 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 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 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 alertsDND(GetDNDBackend()); NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); return alertsDND->SetSuppressForScreenSharing(aSuppress); #endif } already_AddRefed nsAlertsService::GetDNDBackend() { nsCOMPtr backend; // Try the system notification service. if (ShouldUseSystemBackend()) { backend = mBackend; } if (!backend) { backend = nsXULAlerts::GetInstance(); } nsCOMPtr alertsDND(do_QueryInterface(backend)); return alertsDND.forget(); }