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