dom/src/notification/DesktopNotification.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:f430e545c8bc
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 #include "mozilla/dom/DesktopNotification.h"
5 #include "mozilla/dom/DesktopNotificationBinding.h"
6 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
7 #include "nsContentPermissionHelper.h"
8 #include "nsXULAppAPI.h"
9 #include "mozilla/dom/PBrowserChild.h"
10 #include "nsIDOMDesktopNotification.h"
11 #include "TabChild.h"
12 #include "mozilla/Preferences.h"
13 #include "nsGlobalWindow.h"
14 #include "nsIAppsService.h"
15 #include "PCOMContentPermissionRequestChild.h"
16 #include "nsIScriptSecurityManager.h"
17 #include "nsServiceManagerUtils.h"
18 #include "PermissionMessageUtils.h"
19
20 namespace mozilla {
21 namespace dom {
22
23 /*
24 * Simple Request
25 */
26 class DesktopNotificationRequest : public nsIContentPermissionRequest,
27 public nsRunnable,
28 public PCOMContentPermissionRequestChild
29
30 {
31 public:
32 NS_DECL_ISUPPORTS
33 NS_DECL_NSICONTENTPERMISSIONREQUEST
34
35 DesktopNotificationRequest(DesktopNotification* notification)
36 : mDesktopNotification(notification) {}
37
38 NS_IMETHOD Run() MOZ_OVERRIDE
39 {
40 nsCOMPtr<nsIContentPermissionPrompt> prompt =
41 do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
42 if (prompt) {
43 prompt->Prompt(this);
44 }
45 return NS_OK;
46 }
47
48 ~DesktopNotificationRequest()
49 {
50 }
51
52 virtual bool Recv__delete__(const bool& aAllow,
53 const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE
54 {
55 MOZ_ASSERT(choices.IsEmpty(), "DesktopNotification doesn't support permission choice");
56 if (aAllow) {
57 (void) Allow(JS::UndefinedHandleValue);
58 } else {
59 (void) Cancel();
60 }
61 return true;
62 }
63 virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
64
65 nsRefPtr<DesktopNotification> mDesktopNotification;
66 };
67
68 /* ------------------------------------------------------------------------ */
69 /* AlertServiceObserver */
70 /* ------------------------------------------------------------------------ */
71
72 NS_IMPL_ISUPPORTS(AlertServiceObserver, nsIObserver)
73
74 /* ------------------------------------------------------------------------ */
75 /* DesktopNotification */
76 /* ------------------------------------------------------------------------ */
77
78 uint32_t DesktopNotification::sCount = 0;
79
80 nsresult
81 DesktopNotification::PostDesktopNotification()
82 {
83 if (!mObserver) {
84 mObserver = new AlertServiceObserver(this);
85 }
86
87 #ifdef MOZ_B2G
88 nsCOMPtr<nsIAppNotificationService> appNotifier =
89 do_GetService("@mozilla.org/system-alerts-service;1");
90 if (appNotifier) {
91 nsCOMPtr<nsPIDOMWindow> window = GetOwner();
92 uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId();
93
94 if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
95 nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
96 nsString manifestUrl = EmptyString();
97 appsService->GetManifestURLByLocalId(appId, manifestUrl);
98 mozilla::AutoSafeJSContext cx;
99 JS::Rooted<JS::Value> val(cx);
100 AppNotificationServiceOptions ops;
101 ops.mTextClickable = true;
102 ops.mManifestURL = manifestUrl;
103
104 if (!ops.ToObject(cx, &val)) {
105 return NS_ERROR_FAILURE;
106 }
107
108 return appNotifier->ShowAppNotification(mIconURL, mTitle, mDescription,
109 mObserver, val);
110 }
111 }
112 #endif
113
114 nsCOMPtr<nsIAlertsService> alerts = do_GetService("@mozilla.org/alerts-service;1");
115 if (!alerts) {
116 return NS_ERROR_NOT_IMPLEMENTED;
117 }
118
119 // Generate a unique name (which will also be used as a cookie) because
120 // the nsIAlertsService will coalesce notifications with the same name.
121 // In the case of IPC, the parent process will use the cookie to map
122 // to nsIObservers, thus cookies must be unique to differentiate observers.
123 nsString uniqueName = NS_LITERAL_STRING("desktop-notification:");
124 uniqueName.AppendInt(sCount++);
125 nsIPrincipal* principal = GetOwner()->GetDoc()->NodePrincipal();
126 return alerts->ShowAlertNotification(mIconURL, mTitle, mDescription,
127 true,
128 uniqueName,
129 mObserver,
130 uniqueName,
131 NS_LITERAL_STRING("auto"),
132 EmptyString(),
133 principal);
134 }
135
136 DesktopNotification::DesktopNotification(const nsAString & title,
137 const nsAString & description,
138 const nsAString & iconURL,
139 nsPIDOMWindow *aWindow,
140 nsIPrincipal* principal)
141 : DOMEventTargetHelper(aWindow)
142 , mTitle(title)
143 , mDescription(description)
144 , mIconURL(iconURL)
145 , mPrincipal(principal)
146 , mAllow(false)
147 , mShowHasBeenCalled(false)
148 {
149 if (Preferences::GetBool("notification.disabled", false)) {
150 return;
151 }
152
153 // If we are in testing mode (running mochitests, for example)
154 // and we are suppose to allow requests, then just post an allow event.
155 if (Preferences::GetBool("notification.prompt.testing", false) &&
156 Preferences::GetBool("notification.prompt.testing.allow", true)) {
157 mAllow = true;
158 }
159 }
160
161 void
162 DesktopNotification::Init()
163 {
164 nsRefPtr<DesktopNotificationRequest> request = new DesktopNotificationRequest(this);
165
166 // if we are in the content process, then remote it to the parent.
167 if (XRE_GetProcessType() == GeckoProcessType_Content) {
168
169 // if for some reason mOwner is null, just silently
170 // bail. The user will not see a notification, and that
171 // is fine.
172 if (!GetOwner())
173 return;
174
175 // because owner implements nsITabChild, we can assume that it is
176 // the one and only TabChild for this docshell.
177 TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell());
178
179 // Retain a reference so the object isn't deleted without IPDL's knowledge.
180 // Corresponding release occurs in DeallocPContentPermissionRequest.
181 nsRefPtr<DesktopNotificationRequest> copy = request;
182
183 nsTArray<PermissionRequest> permArray;
184 nsTArray<nsString> emptyOptions;
185 permArray.AppendElement(PermissionRequest(
186 NS_LITERAL_CSTRING("desktop-notification"),
187 NS_LITERAL_CSTRING("unused"),
188 emptyOptions));
189 child->SendPContentPermissionRequestConstructor(copy.forget().take(),
190 permArray,
191 IPC::Principal(mPrincipal));
192
193 request->Sendprompt();
194 return;
195 }
196
197 // otherwise, dispatch it
198 NS_DispatchToMainThread(request);
199 }
200
201 DesktopNotification::~DesktopNotification()
202 {
203 if (mObserver) {
204 mObserver->Disconnect();
205 }
206 }
207
208 void
209 DesktopNotification::DispatchNotificationEvent(const nsString& aName)
210 {
211 if (NS_FAILED(CheckInnerWindowCorrectness())) {
212 return;
213 }
214
215 nsCOMPtr<nsIDOMEvent> event;
216 nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
217 if (NS_SUCCEEDED(rv)) {
218 // it doesn't bubble, and it isn't cancelable
219 rv = event->InitEvent(aName, false, false);
220 if (NS_SUCCEEDED(rv)) {
221 event->SetTrusted(true);
222 DispatchDOMEvent(nullptr, event, nullptr, nullptr);
223 }
224 }
225 }
226
227 nsresult
228 DesktopNotification::SetAllow(bool aAllow)
229 {
230 mAllow = aAllow;
231
232 // if we have called Show() already, lets go ahead and post a notification
233 if (mShowHasBeenCalled && aAllow) {
234 return PostDesktopNotification();
235 }
236
237 return NS_OK;
238 }
239
240 void
241 DesktopNotification::HandleAlertServiceNotification(const char *aTopic)
242 {
243 if (NS_FAILED(CheckInnerWindowCorrectness())) {
244 return;
245 }
246
247 if (!strcmp("alertclickcallback", aTopic)) {
248 DispatchNotificationEvent(NS_LITERAL_STRING("click"));
249 } else if (!strcmp("alertfinished", aTopic)) {
250 DispatchNotificationEvent(NS_LITERAL_STRING("close"));
251 }
252 }
253
254 void
255 DesktopNotification::Show(ErrorResult& aRv)
256 {
257 mShowHasBeenCalled = true;
258
259 if (!mAllow) {
260 return;
261 }
262
263 aRv = PostDesktopNotification();
264 }
265
266 JSObject*
267 DesktopNotification::WrapObject(JSContext* aCx)
268 {
269 return DesktopNotificationBinding::Wrap(aCx, this);
270 }
271
272 /* ------------------------------------------------------------------------ */
273 /* DesktopNotificationCenter */
274 /* ------------------------------------------------------------------------ */
275
276 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(DesktopNotificationCenter)
277 NS_IMPL_CYCLE_COLLECTING_ADDREF(DesktopNotificationCenter)
278 NS_IMPL_CYCLE_COLLECTING_RELEASE(DesktopNotificationCenter)
279 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DesktopNotificationCenter)
280 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
281 NS_INTERFACE_MAP_ENTRY(nsISupports)
282 NS_INTERFACE_MAP_END
283
284 already_AddRefed<DesktopNotification>
285 DesktopNotificationCenter::CreateNotification(const nsAString& aTitle,
286 const nsAString& aDescription,
287 const nsAString& aIconURL)
288 {
289 MOZ_ASSERT(mOwner);
290
291 nsRefPtr<DesktopNotification> notification =
292 new DesktopNotification(aTitle,
293 aDescription,
294 aIconURL,
295 mOwner,
296 mPrincipal);
297 notification->Init();
298 return notification.forget();
299 }
300
301 JSObject*
302 DesktopNotificationCenter::WrapObject(JSContext* aCx)
303 {
304 return DesktopNotificationCenterBinding::Wrap(aCx, this);
305 }
306
307 /* ------------------------------------------------------------------------ */
308 /* DesktopNotificationRequest */
309 /* ------------------------------------------------------------------------ */
310
311 NS_IMPL_ISUPPORTS(DesktopNotificationRequest,
312 nsIContentPermissionRequest,
313 nsIRunnable)
314
315 NS_IMETHODIMP
316 DesktopNotificationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
317 {
318 if (!mDesktopNotification) {
319 return NS_ERROR_NOT_INITIALIZED;
320 }
321
322 NS_IF_ADDREF(*aRequestingPrincipal = mDesktopNotification->mPrincipal);
323 return NS_OK;
324 }
325
326 NS_IMETHODIMP
327 DesktopNotificationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow)
328 {
329 if (!mDesktopNotification) {
330 return NS_ERROR_NOT_INITIALIZED;
331 }
332
333 NS_IF_ADDREF(*aRequestingWindow = mDesktopNotification->GetOwner());
334 return NS_OK;
335 }
336
337 NS_IMETHODIMP
338 DesktopNotificationRequest::GetElement(nsIDOMElement * *aElement)
339 {
340 NS_ENSURE_ARG_POINTER(aElement);
341 *aElement = nullptr;
342 return NS_OK;
343 }
344
345 NS_IMETHODIMP
346 DesktopNotificationRequest::Cancel()
347 {
348 nsresult rv = mDesktopNotification->SetAllow(false);
349 mDesktopNotification = nullptr;
350 return rv;
351 }
352
353 NS_IMETHODIMP
354 DesktopNotificationRequest::Allow(JS::HandleValue aChoices)
355 {
356 MOZ_ASSERT(aChoices.isUndefined());
357 nsresult rv = mDesktopNotification->SetAllow(true);
358 mDesktopNotification = nullptr;
359 return rv;
360 }
361
362 NS_IMETHODIMP
363 DesktopNotificationRequest::GetTypes(nsIArray** aTypes)
364 {
365 nsTArray<nsString> emptyOptions;
366 return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
367 NS_LITERAL_CSTRING("unused"),
368 emptyOptions,
369 aTypes);
370 }
371
372 } // namespace dom
373 } // namespace mozilla

mercurial