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