summaryrefslogtreecommitdiffstats
path: root/toolkit/components/alerts/nsAlertsService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/alerts/nsAlertsService.cpp')
-rw-r--r--toolkit/components/alerts/nsAlertsService.cpp342
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();
+}