1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/dom/ContentChild.h"
7 #include "mozilla/dom/PermissionMessageUtils.h"
8 #include "mozilla/Preferences.h"
9 #include "mozilla/Telemetry.h"
10 #include "nsXULAppAPI.h"
11 
12 #include "nsAlertsService.h"
13 
14 #include "nsXPCOM.h"
15 #include "nsIServiceManager.h"
16 #include "nsIDOMWindow.h"
17 #include "nsPromiseFlatString.h"
18 #include "nsToolkitCompsCID.h"
19 
20 #ifdef MOZ_PLACES
21 #include "mozIAsyncFavicons.h"
22 #include "nsIFaviconService.h"
23 #endif  // MOZ_PLACES
24 
25 #ifdef XP_WIN
26 #include <shellapi.h>
27 #endif
28 
29 using namespace mozilla;
30 
31 using mozilla::dom::ContentChild;
32 
33 namespace {
34 
35 #ifdef MOZ_PLACES
36 
37 class IconCallback final : public nsIFaviconDataCallback {
38  public:
39   NS_DECL_ISUPPORTS
40 
IconCallback(nsIAlertsService * aBackend,nsIAlertNotification * aAlert,nsIObserver * aAlertListener)41   IconCallback(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
42                nsIObserver* aAlertListener)
43       : mBackend(aBackend), mAlert(aAlert), mAlertListener(aAlertListener) {}
44 
45   NS_IMETHOD
OnComplete(nsIURI * aIconURI,uint32_t aIconSize,const uint8_t * aIconData,const nsACString & aMimeType,uint16_t aWidth)46   OnComplete(nsIURI* aIconURI, uint32_t aIconSize, const uint8_t* aIconData,
47              const nsACString& aMimeType, uint16_t aWidth) override {
48     nsresult rv = NS_ERROR_FAILURE;
49     if (aIconSize > 0) {
50       nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend));
51       if (alertsIconData) {
52         rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener,
53                                                    aIconSize, aIconData);
54       }
55     } else if (aIconURI) {
56       nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend));
57       if (alertsIconURI) {
58         rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener,
59                                                  aIconURI);
60       }
61     }
62     if (NS_FAILED(rv)) {
63       rv = mBackend->ShowAlert(mAlert, mAlertListener);
64     }
65     return rv;
66   }
67 
68  private:
~IconCallback()69   virtual ~IconCallback() {}
70 
71   nsCOMPtr<nsIAlertsService> mBackend;
72   nsCOMPtr<nsIAlertNotification> mAlert;
73   nsCOMPtr<nsIObserver> mAlertListener;
74 };
75 
NS_IMPL_ISUPPORTS(IconCallback,nsIFaviconDataCallback)76 NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback)
77 
78 #endif  // MOZ_PLACES
79 
80 nsresult ShowWithIconBackend(nsIAlertsService* aBackend,
81                              nsIAlertNotification* aAlert,
82                              nsIObserver* aAlertListener) {
83 #ifdef MOZ_PLACES
84   nsCOMPtr<nsIURI> uri;
85   nsresult rv = aAlert->GetURI(getter_AddRefs(uri));
86   if (NS_FAILED(rv) || !uri) {
87     return NS_ERROR_FAILURE;
88   }
89 
90   // Ensure the backend supports favicons.
91   nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend));
92   nsCOMPtr<nsIAlertsIconURI> alertsIconURI;
93   if (!alertsIconData) {
94     alertsIconURI = do_QueryInterface(aBackend);
95   }
96   if (!alertsIconData && !alertsIconURI) {
97     return NS_ERROR_NOT_IMPLEMENTED;
98   }
99 
100   nsCOMPtr<mozIAsyncFavicons> favicons(
101       do_GetService("@mozilla.org/browser/favicon-service;1"));
102   NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE);
103 
104   nsCOMPtr<nsIFaviconDataCallback> callback =
105       new IconCallback(aBackend, aAlert, aAlertListener);
106   if (alertsIconData) {
107     return favicons->GetFaviconDataForPage(uri, callback, 0);
108   }
109   return favicons->GetFaviconURLForPage(uri, callback, 0);
110 #else
111   return NS_ERROR_NOT_IMPLEMENTED;
112 #endif  // !MOZ_PLACES
113 }
114 
ShowWithBackend(nsIAlertsService * aBackend,nsIAlertNotification * aAlert,nsIObserver * aAlertListener,const nsAString & aPersistentData)115 nsresult ShowWithBackend(nsIAlertsService* aBackend,
116                          nsIAlertNotification* aAlert,
117                          nsIObserver* aAlertListener,
118                          const nsAString& aPersistentData) {
119   if (!aPersistentData.IsEmpty()) {
120     return aBackend->ShowPersistentNotification(aPersistentData, aAlert,
121                                                 aAlertListener);
122   }
123 
124   if (Preferences::GetBool("alerts.showFavicons")) {
125     nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener);
126     if (NS_SUCCEEDED(rv)) {
127       return rv;
128     }
129   }
130 
131   // If favicons are disabled, or the backend doesn't support them, show the
132   // alert without one.
133   return aBackend->ShowAlert(aAlert, aAlertListener);
134 }
135 
136 }  // anonymous namespace
137 
NS_IMPL_ISUPPORTS(nsAlertsService,nsIAlertsService,nsIAlertsDoNotDisturb)138 NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb)
139 
140 nsAlertsService::nsAlertsService() : mBackend(nullptr) {
141   mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID);
142 }
143 
~nsAlertsService()144 nsAlertsService::~nsAlertsService() {}
145 
ShouldShowAlert()146 bool nsAlertsService::ShouldShowAlert() {
147   bool result = true;
148 
149 #ifdef XP_WIN
150   QUERY_USER_NOTIFICATION_STATE qstate;
151   if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) {
152     if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
153       result = false;
154     }
155   }
156 #endif
157 
158   return result;
159 }
160 
ShouldUseSystemBackend()161 bool nsAlertsService::ShouldUseSystemBackend() {
162   if (!mBackend) {
163     return false;
164   }
165   static bool sAlertsUseSystemBackend;
166   static bool sAlertsUseSystemBackendCached = false;
167   if (!sAlertsUseSystemBackendCached) {
168     sAlertsUseSystemBackendCached = true;
169     Preferences::AddBoolVarCache(&sAlertsUseSystemBackend,
170                                  "alerts.useSystemBackend", true);
171   }
172   return sAlertsUseSystemBackend;
173 }
174 
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)175 NS_IMETHODIMP nsAlertsService::ShowAlertNotification(
176     const nsAString& aImageUrl, const nsAString& aAlertTitle,
177     const nsAString& aAlertText, bool aAlertTextClickable,
178     const nsAString& aAlertCookie, nsIObserver* aAlertListener,
179     const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
180     const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
181     bool aRequireInteraction) {
182   nsCOMPtr<nsIAlertNotification> alert =
183       do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
184   NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
185   nsresult rv =
186       alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
187                   aAlertTextClickable, aAlertCookie, aBidi, aLang, aData,
188                   aPrincipal, aInPrivateBrowsing, aRequireInteraction);
189   NS_ENSURE_SUCCESS(rv, rv);
190   return ShowAlert(alert, aAlertListener);
191 }
192 
ShowAlert(nsIAlertNotification * aAlert,nsIObserver * aAlertListener)193 NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification* aAlert,
194                                          nsIObserver* aAlertListener) {
195   return ShowPersistentNotification(EmptyString(), aAlert, aAlertListener);
196 }
197 
ShowPersistentNotification(const nsAString & aPersistentData,nsIAlertNotification * aAlert,nsIObserver * aAlertListener)198 NS_IMETHODIMP nsAlertsService::ShowPersistentNotification(
199     const nsAString& aPersistentData, nsIAlertNotification* aAlert,
200     nsIObserver* aAlertListener) {
201   NS_ENSURE_ARG(aAlert);
202 
203   nsAutoString cookie;
204   nsresult rv = aAlert->GetCookie(cookie);
205   NS_ENSURE_SUCCESS(rv, rv);
206 
207   if (XRE_IsContentProcess()) {
208     ContentChild* cpc = ContentChild::GetSingleton();
209 
210     if (aAlertListener) cpc->AddRemoteAlertObserver(cookie, aAlertListener);
211 
212     cpc->SendShowAlert(aAlert);
213     return NS_OK;
214   }
215 
216   // Check if there is an optional service that handles system-level
217   // notifications
218   if (ShouldUseSystemBackend()) {
219     rv = ShowWithBackend(mBackend, aAlert, aAlertListener, aPersistentData);
220     if (NS_SUCCEEDED(rv)) {
221       return rv;
222     }
223     // If the system backend failed to show the alert, clear the backend and
224     // retry with XUL notifications. Future alerts will always use XUL.
225     mBackend = nullptr;
226   }
227 
228   if (!ShouldShowAlert()) {
229     // Do not display the alert. Instead call alertfinished and get out.
230     if (aAlertListener)
231       aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
232     return NS_OK;
233   }
234 
235   // Use XUL notifications as a fallback if above methods have failed.
236   nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
237   NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
238   return ShowWithBackend(xulBackend, aAlert, aAlertListener, aPersistentData);
239 }
240 
CloseAlert(const nsAString & aAlertName,nsIPrincipal * aPrincipal)241 NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName,
242                                           nsIPrincipal* aPrincipal) {
243   if (XRE_IsContentProcess()) {
244     ContentChild* cpc = ContentChild::GetSingleton();
245     cpc->SendCloseAlert(nsAutoString(aAlertName), IPC::Principal(aPrincipal));
246     return NS_OK;
247   }
248 
249   nsresult rv;
250   // Try the system notification service.
251   if (ShouldUseSystemBackend()) {
252     rv = mBackend->CloseAlert(aAlertName, aPrincipal);
253     if (NS_WARN_IF(NS_FAILED(rv))) {
254       // If the system backend failed to close the alert, fall back to XUL for
255       // future alerts.
256       mBackend = nullptr;
257     }
258   } else {
259     nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
260     NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
261     rv = xulBackend->CloseAlert(aAlertName, aPrincipal);
262   }
263   return rv;
264 }
265 
266 // nsIAlertsDoNotDisturb
GetManualDoNotDisturb(bool * aRetVal)267 NS_IMETHODIMP nsAlertsService::GetManualDoNotDisturb(bool* aRetVal) {
268 #ifdef MOZ_WIDGET_ANDROID
269   return NS_ERROR_NOT_IMPLEMENTED;
270 #else
271   nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
272   NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
273   return alertsDND->GetManualDoNotDisturb(aRetVal);
274 #endif
275 }
276 
SetManualDoNotDisturb(bool aDoNotDisturb)277 NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) {
278 #ifdef MOZ_WIDGET_ANDROID
279   return NS_ERROR_NOT_IMPLEMENTED;
280 #else
281   nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
282   NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
283 
284   nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb);
285   if (NS_SUCCEEDED(rv)) {
286     Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1);
287   }
288   return rv;
289 #endif
290 }
291 
GetDNDBackend()292 already_AddRefed<nsIAlertsDoNotDisturb> nsAlertsService::GetDNDBackend() {
293   nsCOMPtr<nsIAlertsService> backend;
294   // Try the system notification service.
295   if (ShouldUseSystemBackend()) {
296     backend = mBackend;
297   }
298   if (!backend) {
299     backend = nsXULAlerts::GetInstance();
300   }
301 
302   nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(do_QueryInterface(backend));
303   return alertsDND.forget();
304 }
305