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