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