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 "nsXULAlerts.h"
7
8 #include "nsArray.h"
9 #include "nsComponentManagerUtils.h"
10 #include "nsCOMPtr.h"
11 #include "mozilla/ClearOnShutdown.h"
12 #include "mozilla/LookAndFeel.h"
13 #include "mozilla/dom/Notification.h"
14 #include "mozilla/Unused.h"
15 #include "nsIServiceManager.h"
16 #include "nsISupportsPrimitives.h"
17 #include "nsPIDOMWindow.h"
18 #include "nsIWindowWatcher.h"
19
20 using namespace mozilla;
21 using mozilla::dom::NotificationTelemetryService;
22
23 #define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul"
24
25 namespace {
26 StaticRefPtr<nsXULAlerts> gXULAlerts;
27 } // anonymous namespace
28
NS_IMPL_CYCLE_COLLECTION(nsXULAlertObserver,mAlertWindow)29 NS_IMPL_CYCLE_COLLECTION(nsXULAlertObserver, mAlertWindow)
30
31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULAlertObserver)
32 NS_INTERFACE_MAP_ENTRY(nsIObserver)
33 NS_INTERFACE_MAP_ENTRY(nsISupports)
34 NS_INTERFACE_MAP_END
35
36 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULAlertObserver)
37 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULAlertObserver)
38
39 NS_IMETHODIMP
40 nsXULAlertObserver::Observe(nsISupports* aSubject, const char* aTopic,
41 const char16_t* aData) {
42 if (!strcmp("alertfinished", aTopic)) {
43 mozIDOMWindowProxy* currentAlert =
44 mXULAlerts->mNamedWindows.GetWeak(mAlertName);
45 // The window in mNamedWindows might be a replacement, thus it should only
46 // be removed if it is the same window that is associated with this
47 // listener.
48 if (currentAlert == mAlertWindow) {
49 mXULAlerts->mNamedWindows.Remove(mAlertName);
50
51 if (mIsPersistent) {
52 mXULAlerts->PersistentAlertFinished();
53 }
54 }
55 }
56
57 nsresult rv = NS_OK;
58 if (mObserver) {
59 rv = mObserver->Observe(aSubject, aTopic, aData);
60 }
61 return rv;
62 }
63
64 // We don't cycle collect nsXULAlerts since gXULAlerts will keep the instance
65 // alive till shutdown anyway.
NS_IMPL_ISUPPORTS(nsXULAlerts,nsIAlertsService,nsIAlertsDoNotDisturb,nsIAlertsIconURI)66 NS_IMPL_ISUPPORTS(nsXULAlerts, nsIAlertsService, nsIAlertsDoNotDisturb,
67 nsIAlertsIconURI)
68
69 /* static */ already_AddRefed<nsXULAlerts> nsXULAlerts::GetInstance() {
70 // Gecko on Android does not fully support XUL windows.
71 #ifndef MOZ_WIDGET_ANDROID
72 if (!gXULAlerts) {
73 gXULAlerts = new nsXULAlerts();
74 ClearOnShutdown(&gXULAlerts);
75 }
76 #endif // MOZ_WIDGET_ANDROID
77 RefPtr<nsXULAlerts> instance = gXULAlerts.get();
78 return instance.forget();
79 }
80
PersistentAlertFinished()81 void nsXULAlerts::PersistentAlertFinished() {
82 MOZ_ASSERT(mPersistentAlertCount);
83 mPersistentAlertCount--;
84
85 // Show next pending persistent alert if any.
86 if (!mPendingPersistentAlerts.IsEmpty()) {
87 ShowAlertWithIconURI(mPendingPersistentAlerts[0].mAlert,
88 mPendingPersistentAlerts[0].mListener, nullptr);
89 mPendingPersistentAlerts.RemoveElementAt(0);
90 }
91 }
92
93 NS_IMETHODIMP
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)94 nsXULAlerts::ShowAlertNotification(
95 const nsAString& aImageUrl, const nsAString& aAlertTitle,
96 const nsAString& aAlertText, bool aAlertTextClickable,
97 const nsAString& aAlertCookie, nsIObserver* aAlertListener,
98 const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
99 const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
100 bool aRequireInteraction) {
101 nsCOMPtr<nsIAlertNotification> alert =
102 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
103 NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
104 nsresult rv =
105 alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
106 aAlertTextClickable, aAlertCookie, aBidi, aLang, aData,
107 aPrincipal, aInPrivateBrowsing, aRequireInteraction);
108 NS_ENSURE_SUCCESS(rv, rv);
109 return ShowAlert(alert, aAlertListener);
110 }
111
112 NS_IMETHODIMP
ShowPersistentNotification(const nsAString & aPersistentData,nsIAlertNotification * aAlert,nsIObserver * aAlertListener)113 nsXULAlerts::ShowPersistentNotification(const nsAString& aPersistentData,
114 nsIAlertNotification* aAlert,
115 nsIObserver* aAlertListener) {
116 return ShowAlert(aAlert, aAlertListener);
117 }
118
119 NS_IMETHODIMP
ShowAlert(nsIAlertNotification * aAlert,nsIObserver * aAlertListener)120 nsXULAlerts::ShowAlert(nsIAlertNotification* aAlert,
121 nsIObserver* aAlertListener) {
122 nsAutoString name;
123 nsresult rv = aAlert->GetName(name);
124 NS_ENSURE_SUCCESS(rv, rv);
125
126 // If there is a pending alert with the same name in the list of
127 // pending alerts, replace it.
128 if (!mPendingPersistentAlerts.IsEmpty()) {
129 for (uint32_t i = 0; i < mPendingPersistentAlerts.Length(); i++) {
130 nsAutoString pendingAlertName;
131 nsCOMPtr<nsIAlertNotification> pendingAlert =
132 mPendingPersistentAlerts[i].mAlert;
133 rv = pendingAlert->GetName(pendingAlertName);
134 NS_ENSURE_SUCCESS(rv, rv);
135
136 if (pendingAlertName.Equals(name)) {
137 nsAutoString cookie;
138 rv = pendingAlert->GetCookie(cookie);
139 NS_ENSURE_SUCCESS(rv, rv);
140
141 if (mPendingPersistentAlerts[i].mListener) {
142 rv = mPendingPersistentAlerts[i].mListener->Observe(
143 nullptr, "alertfinished", cookie.get());
144 NS_ENSURE_SUCCESS(rv, rv);
145 }
146
147 mPendingPersistentAlerts[i].Init(aAlert, aAlertListener);
148 return NS_OK;
149 }
150 }
151 }
152
153 bool requireInteraction;
154 rv = aAlert->GetRequireInteraction(&requireInteraction);
155 NS_ENSURE_SUCCESS(rv, rv);
156
157 if (requireInteraction && !mNamedWindows.Contains(name) &&
158 static_cast<int32_t>(mPersistentAlertCount) >=
159 Preferences::GetInt("dom.webnotifications.requireinteraction.count",
160 0)) {
161 PendingAlert* pa = mPendingPersistentAlerts.AppendElement();
162 pa->Init(aAlert, aAlertListener);
163 return NS_OK;
164 } else {
165 return ShowAlertWithIconURI(aAlert, aAlertListener, nullptr);
166 }
167 }
168
169 NS_IMETHODIMP
ShowAlertWithIconURI(nsIAlertNotification * aAlert,nsIObserver * aAlertListener,nsIURI * aIconURI)170 nsXULAlerts::ShowAlertWithIconURI(nsIAlertNotification* aAlert,
171 nsIObserver* aAlertListener,
172 nsIURI* aIconURI) {
173 bool inPrivateBrowsing;
174 nsresult rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
175 NS_ENSURE_SUCCESS(rv, rv);
176
177 nsAutoString cookie;
178 rv = aAlert->GetCookie(cookie);
179 NS_ENSURE_SUCCESS(rv, rv);
180
181 if (mDoNotDisturb) {
182 if (aAlertListener)
183 aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
184 return NS_OK;
185 }
186
187 nsAutoString name;
188 rv = aAlert->GetName(name);
189 NS_ENSURE_SUCCESS(rv, rv);
190
191 nsAutoString imageUrl;
192 rv = aAlert->GetImageURL(imageUrl);
193 NS_ENSURE_SUCCESS(rv, rv);
194
195 nsAutoString title;
196 rv = aAlert->GetTitle(title);
197 NS_ENSURE_SUCCESS(rv, rv);
198
199 nsAutoString text;
200 rv = aAlert->GetText(text);
201 NS_ENSURE_SUCCESS(rv, rv);
202
203 bool textClickable;
204 rv = aAlert->GetTextClickable(&textClickable);
205 NS_ENSURE_SUCCESS(rv, rv);
206
207 nsAutoString bidi;
208 rv = aAlert->GetDir(bidi);
209 NS_ENSURE_SUCCESS(rv, rv);
210
211 nsAutoString lang;
212 rv = aAlert->GetLang(lang);
213 NS_ENSURE_SUCCESS(rv, rv);
214
215 nsAutoString source;
216 rv = aAlert->GetSource(source);
217 NS_ENSURE_SUCCESS(rv, rv);
218
219 bool requireInteraction;
220 rv = aAlert->GetRequireInteraction(&requireInteraction);
221 NS_ENSURE_SUCCESS(rv, rv);
222
223 nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
224
225 nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create();
226
227 // create scriptable versions of our strings that we can store in our
228 // nsIMutableArray....
229 nsCOMPtr<nsISupportsString> scriptableImageUrl(
230 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
231 NS_ENSURE_TRUE(scriptableImageUrl, NS_ERROR_FAILURE);
232
233 scriptableImageUrl->SetData(imageUrl);
234 rv = argsArray->AppendElement(scriptableImageUrl);
235 NS_ENSURE_SUCCESS(rv, rv);
236
237 nsCOMPtr<nsISupportsString> scriptableAlertTitle(
238 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
239 NS_ENSURE_TRUE(scriptableAlertTitle, NS_ERROR_FAILURE);
240
241 scriptableAlertTitle->SetData(title);
242 rv = argsArray->AppendElement(scriptableAlertTitle);
243 NS_ENSURE_SUCCESS(rv, rv);
244
245 nsCOMPtr<nsISupportsString> scriptableAlertText(
246 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
247 NS_ENSURE_TRUE(scriptableAlertText, NS_ERROR_FAILURE);
248
249 scriptableAlertText->SetData(text);
250 rv = argsArray->AppendElement(scriptableAlertText);
251 NS_ENSURE_SUCCESS(rv, rv);
252
253 nsCOMPtr<nsISupportsPRBool> scriptableIsClickable(
254 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
255 NS_ENSURE_TRUE(scriptableIsClickable, NS_ERROR_FAILURE);
256
257 scriptableIsClickable->SetData(textClickable);
258 rv = argsArray->AppendElement(scriptableIsClickable);
259 NS_ENSURE_SUCCESS(rv, rv);
260
261 nsCOMPtr<nsISupportsString> scriptableAlertCookie(
262 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
263 NS_ENSURE_TRUE(scriptableAlertCookie, NS_ERROR_FAILURE);
264
265 scriptableAlertCookie->SetData(cookie);
266 rv = argsArray->AppendElement(scriptableAlertCookie);
267 NS_ENSURE_SUCCESS(rv, rv);
268
269 nsCOMPtr<nsISupportsPRInt32> scriptableOrigin(
270 do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID));
271 NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE);
272
273 int32_t origin =
274 LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin);
275 scriptableOrigin->SetData(origin);
276
277 rv = argsArray->AppendElement(scriptableOrigin);
278 NS_ENSURE_SUCCESS(rv, rv);
279
280 nsCOMPtr<nsISupportsString> scriptableBidi(
281 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
282 NS_ENSURE_TRUE(scriptableBidi, NS_ERROR_FAILURE);
283
284 scriptableBidi->SetData(bidi);
285 rv = argsArray->AppendElement(scriptableBidi);
286 NS_ENSURE_SUCCESS(rv, rv);
287
288 nsCOMPtr<nsISupportsString> scriptableLang(
289 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
290 NS_ENSURE_TRUE(scriptableLang, NS_ERROR_FAILURE);
291
292 scriptableLang->SetData(lang);
293 rv = argsArray->AppendElement(scriptableLang);
294 NS_ENSURE_SUCCESS(rv, rv);
295
296 nsCOMPtr<nsISupportsPRBool> scriptableRequireInteraction(
297 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
298 NS_ENSURE_TRUE(scriptableRequireInteraction, NS_ERROR_FAILURE);
299
300 scriptableRequireInteraction->SetData(requireInteraction);
301 rv = argsArray->AppendElement(scriptableRequireInteraction);
302 NS_ENSURE_SUCCESS(rv, rv);
303
304 // Alerts with the same name should replace the old alert in the same
305 // position. Provide the new alert window with a pointer to the replaced
306 // window so that it may take the same position.
307 nsCOMPtr<nsISupportsInterfacePointer> replacedWindow =
308 do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
309 NS_ENSURE_TRUE(replacedWindow, NS_ERROR_FAILURE);
310 mozIDOMWindowProxy* previousAlert = mNamedWindows.GetWeak(name);
311 replacedWindow->SetData(previousAlert);
312 replacedWindow->SetDataIID(&NS_GET_IID(mozIDOMWindowProxy));
313 rv = argsArray->AppendElement(replacedWindow);
314 NS_ENSURE_SUCCESS(rv, rv);
315
316 if (requireInteraction) {
317 mPersistentAlertCount++;
318 }
319
320 // Add an observer (that wraps aAlertListener) to remove the window from
321 // mNamedWindows when it is closed.
322 nsCOMPtr<nsISupportsInterfacePointer> ifptr =
323 do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
324 NS_ENSURE_SUCCESS(rv, rv);
325 RefPtr<nsXULAlertObserver> alertObserver =
326 new nsXULAlertObserver(this, name, aAlertListener, requireInteraction);
327 nsCOMPtr<nsISupports> iSupports(do_QueryInterface(alertObserver));
328 ifptr->SetData(iSupports);
329 ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
330 rv = argsArray->AppendElement(ifptr);
331 NS_ENSURE_SUCCESS(rv, rv);
332
333 // The source contains the host and port of the site that sent the
334 // notification. It is empty for system alerts.
335 nsCOMPtr<nsISupportsString> scriptableAlertSource(
336 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
337 NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE);
338 scriptableAlertSource->SetData(source);
339 rv = argsArray->AppendElement(scriptableAlertSource);
340 NS_ENSURE_SUCCESS(rv, rv);
341
342 nsCOMPtr<nsISupportsCString> scriptableIconURL(
343 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
344 NS_ENSURE_TRUE(scriptableIconURL, NS_ERROR_FAILURE);
345 if (aIconURI) {
346 nsAutoCString iconURL;
347 rv = aIconURI->GetSpec(iconURL);
348 NS_ENSURE_SUCCESS(rv, rv);
349 scriptableIconURL->SetData(iconURL);
350 }
351 rv = argsArray->AppendElement(scriptableIconURL);
352 NS_ENSURE_SUCCESS(rv, rv);
353
354 nsCOMPtr<mozIDOMWindowProxy> newWindow;
355 nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes");
356 if (inPrivateBrowsing) {
357 features.AppendLiteral(",private");
358 }
359 rv = wwatch->OpenWindow(nullptr, ALERT_CHROME_URL, "_blank", features.get(),
360 argsArray, getter_AddRefs(newWindow));
361 NS_ENSURE_SUCCESS(rv, rv);
362
363 mNamedWindows.Put(name, newWindow);
364 alertObserver->SetAlertWindow(newWindow);
365
366 return NS_OK;
367 }
368
369 NS_IMETHODIMP
SetManualDoNotDisturb(bool aDoNotDisturb)370 nsXULAlerts::SetManualDoNotDisturb(bool aDoNotDisturb) {
371 mDoNotDisturb = aDoNotDisturb;
372 return NS_OK;
373 }
374
375 NS_IMETHODIMP
GetManualDoNotDisturb(bool * aRetVal)376 nsXULAlerts::GetManualDoNotDisturb(bool* aRetVal) {
377 *aRetVal = mDoNotDisturb;
378 return NS_OK;
379 }
380
381 NS_IMETHODIMP
CloseAlert(const nsAString & aAlertName,nsIPrincipal * aPrincipal)382 nsXULAlerts::CloseAlert(const nsAString& aAlertName, nsIPrincipal* aPrincipal) {
383 mozIDOMWindowProxy* alert = mNamedWindows.GetWeak(aAlertName);
384 if (nsCOMPtr<nsPIDOMWindowOuter> domWindow =
385 nsPIDOMWindowOuter::From(alert)) {
386 domWindow->DispatchCustomEvent(NS_LITERAL_STRING("XULAlertClose"));
387 }
388 return NS_OK;
389 }
390