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