|
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 |
|
5 #include "PCOMContentPermissionRequestChild.h" |
|
6 #include "mozilla/dom/Notification.h" |
|
7 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" |
|
8 #include "mozilla/dom/OwningNonNull.h" |
|
9 #include "mozilla/dom/Promise.h" |
|
10 #include "mozilla/Preferences.h" |
|
11 #include "TabChild.h" |
|
12 #include "nsContentUtils.h" |
|
13 #include "nsIAlertsService.h" |
|
14 #include "nsIAppsService.h" |
|
15 #include "nsIContentPermissionPrompt.h" |
|
16 #include "nsIDocument.h" |
|
17 #include "nsINotificationStorage.h" |
|
18 #include "nsIPermissionManager.h" |
|
19 #include "nsIUUIDGenerator.h" |
|
20 #include "nsServiceManagerUtils.h" |
|
21 #include "nsToolkitCompsCID.h" |
|
22 #include "nsGlobalWindow.h" |
|
23 #include "nsDOMJSUtils.h" |
|
24 #include "nsIScriptSecurityManager.h" |
|
25 #include "mozilla/dom/PermissionMessageUtils.h" |
|
26 #include "nsContentPermissionHelper.h" |
|
27 #ifdef MOZ_B2G |
|
28 #include "nsIDOMDesktopNotification.h" |
|
29 #endif |
|
30 |
|
31 namespace mozilla { |
|
32 namespace dom { |
|
33 |
|
34 class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback |
|
35 { |
|
36 public: |
|
37 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
38 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback) |
|
39 |
|
40 NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise) |
|
41 : mCount(0), |
|
42 mGlobal(aGlobal.Get()), |
|
43 mWindow(aWindow), |
|
44 mPromise(aPromise) |
|
45 { |
|
46 MOZ_ASSERT(aWindow); |
|
47 MOZ_ASSERT(aPromise); |
|
48 JSContext* cx = aGlobal.GetContext(); |
|
49 JSAutoCompartment ac(cx, mGlobal); |
|
50 mNotifications = JS_NewArrayObject(cx, 0); |
|
51 HoldData(); |
|
52 } |
|
53 |
|
54 NS_IMETHOD Handle(const nsAString& aID, |
|
55 const nsAString& aTitle, |
|
56 const nsAString& aDir, |
|
57 const nsAString& aLang, |
|
58 const nsAString& aBody, |
|
59 const nsAString& aTag, |
|
60 const nsAString& aIcon, |
|
61 JSContext* aCx) |
|
62 { |
|
63 MOZ_ASSERT(!aID.IsEmpty()); |
|
64 |
|
65 NotificationOptions options; |
|
66 options.mDir = Notification::StringToDirection(nsString(aDir)); |
|
67 options.mLang = aLang; |
|
68 options.mBody = aBody; |
|
69 options.mTag = aTag; |
|
70 options.mIcon = aIcon; |
|
71 nsRefPtr<Notification> notification = Notification::CreateInternal(mWindow, |
|
72 aID, |
|
73 aTitle, |
|
74 options); |
|
75 JSAutoCompartment ac(aCx, mGlobal); |
|
76 JS::Rooted<JSObject*> element(aCx, notification->WrapObject(aCx)); |
|
77 NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); |
|
78 |
|
79 if (!JS_DefineElement(aCx, mNotifications, mCount++, |
|
80 JS::ObjectValue(*element), nullptr, nullptr, 0)) { |
|
81 return NS_ERROR_FAILURE; |
|
82 } |
|
83 return NS_OK; |
|
84 } |
|
85 |
|
86 NS_IMETHOD Done(JSContext* aCx) |
|
87 { |
|
88 JSAutoCompartment ac(aCx, mGlobal); |
|
89 JS::Rooted<JS::Value> result(aCx, JS::ObjectValue(*mNotifications)); |
|
90 mPromise->MaybeResolve(aCx, result); |
|
91 return NS_OK; |
|
92 } |
|
93 |
|
94 private: |
|
95 ~NotificationStorageCallback() |
|
96 { |
|
97 DropData(); |
|
98 } |
|
99 |
|
100 void HoldData() |
|
101 { |
|
102 mozilla::HoldJSObjects(this); |
|
103 } |
|
104 |
|
105 void DropData() |
|
106 { |
|
107 mGlobal = nullptr; |
|
108 mNotifications = nullptr; |
|
109 mozilla::DropJSObjects(this); |
|
110 } |
|
111 |
|
112 uint32_t mCount; |
|
113 JS::Heap<JSObject *> mGlobal; |
|
114 nsCOMPtr<nsPIDOMWindow> mWindow; |
|
115 nsRefPtr<Promise> mPromise; |
|
116 JS::Heap<JSObject *> mNotifications; |
|
117 }; |
|
118 |
|
119 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback) |
|
120 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback) |
|
121 NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback) |
|
122 |
|
123 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback) |
|
124 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback) |
|
125 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
126 NS_INTERFACE_MAP_END |
|
127 |
|
128 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback) |
|
129 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) |
|
130 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications) |
|
131 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
132 |
|
133 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback) |
|
134 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
135 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) |
|
136 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) |
|
137 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
138 |
|
139 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback) |
|
140 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) |
|
141 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) |
|
142 tmp->DropData(); |
|
143 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
144 |
|
145 class NotificationPermissionRequest : public nsIContentPermissionRequest, |
|
146 public PCOMContentPermissionRequestChild, |
|
147 public nsIRunnable |
|
148 { |
|
149 public: |
|
150 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
151 NS_DECL_NSICONTENTPERMISSIONREQUEST |
|
152 NS_DECL_NSIRUNNABLE |
|
153 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest, |
|
154 nsIContentPermissionRequest) |
|
155 |
|
156 NotificationPermissionRequest(nsIPrincipal* aPrincipal, nsPIDOMWindow* aWindow, |
|
157 NotificationPermissionCallback* aCallback) |
|
158 : mPrincipal(aPrincipal), mWindow(aWindow), |
|
159 mPermission(NotificationPermission::Default), |
|
160 mCallback(aCallback) {} |
|
161 |
|
162 virtual ~NotificationPermissionRequest() {} |
|
163 |
|
164 bool Recv__delete__(const bool& aAllow, |
|
165 const InfallibleTArray<PermissionChoice>& choices); |
|
166 void IPDLRelease() { Release(); } |
|
167 |
|
168 protected: |
|
169 nsresult CallCallback(); |
|
170 nsresult DispatchCallback(); |
|
171 nsCOMPtr<nsIPrincipal> mPrincipal; |
|
172 nsCOMPtr<nsPIDOMWindow> mWindow; |
|
173 NotificationPermission mPermission; |
|
174 nsRefPtr<NotificationPermissionCallback> mCallback; |
|
175 }; |
|
176 |
|
177 class NotificationObserver : public nsIObserver |
|
178 { |
|
179 public: |
|
180 NS_DECL_ISUPPORTS |
|
181 NS_DECL_NSIOBSERVER |
|
182 |
|
183 NotificationObserver(Notification* aNotification) |
|
184 : mNotification(aNotification) {} |
|
185 |
|
186 virtual ~NotificationObserver() {} |
|
187 |
|
188 protected: |
|
189 nsRefPtr<Notification> mNotification; |
|
190 }; |
|
191 |
|
192 class NotificationTask : public nsIRunnable |
|
193 { |
|
194 public: |
|
195 NS_DECL_ISUPPORTS |
|
196 NS_DECL_NSIRUNNABLE |
|
197 |
|
198 enum NotificationAction { |
|
199 eShow, |
|
200 eClose |
|
201 }; |
|
202 |
|
203 NotificationTask(Notification* aNotification, NotificationAction aAction) |
|
204 : mNotification(aNotification), mAction(aAction) {} |
|
205 |
|
206 virtual ~NotificationTask() {} |
|
207 |
|
208 protected: |
|
209 nsRefPtr<Notification> mNotification; |
|
210 NotificationAction mAction; |
|
211 }; |
|
212 |
|
213 uint32_t Notification::sCount = 0; |
|
214 |
|
215 NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow) |
|
216 |
|
217 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest) |
|
218 NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) |
|
219 NS_INTERFACE_MAP_ENTRY(nsIRunnable) |
|
220 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) |
|
221 NS_INTERFACE_MAP_END |
|
222 |
|
223 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationPermissionRequest) |
|
224 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationPermissionRequest) |
|
225 |
|
226 NS_IMETHODIMP |
|
227 NotificationPermissionRequest::Run() |
|
228 { |
|
229 if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { |
|
230 mPermission = NotificationPermission::Granted; |
|
231 } else { |
|
232 // File are automatically granted permission. |
|
233 nsCOMPtr<nsIURI> uri; |
|
234 mPrincipal->GetURI(getter_AddRefs(uri)); |
|
235 |
|
236 if (uri) { |
|
237 bool isFile; |
|
238 uri->SchemeIs("file", &isFile); |
|
239 if (isFile) { |
|
240 mPermission = NotificationPermission::Granted; |
|
241 } |
|
242 } |
|
243 } |
|
244 |
|
245 // Grant permission if pref'ed on. |
|
246 if (Preferences::GetBool("notification.prompt.testing", false)) { |
|
247 if (Preferences::GetBool("notification.prompt.testing.allow", true)) { |
|
248 mPermission = NotificationPermission::Granted; |
|
249 } else { |
|
250 mPermission = NotificationPermission::Denied; |
|
251 } |
|
252 } |
|
253 |
|
254 if (mPermission != NotificationPermission::Default) { |
|
255 return DispatchCallback(); |
|
256 } |
|
257 |
|
258 if (XRE_GetProcessType() == GeckoProcessType_Content) { |
|
259 // because owner implements nsITabChild, we can assume that it is |
|
260 // the one and only TabChild. |
|
261 TabChild* child = TabChild::GetFrom(mWindow->GetDocShell()); |
|
262 if (!child) { |
|
263 return NS_ERROR_NOT_AVAILABLE; |
|
264 } |
|
265 |
|
266 // Retain a reference so the object isn't deleted without IPDL's knowledge. |
|
267 // Corresponding release occurs in DeallocPContentPermissionRequest. |
|
268 AddRef(); |
|
269 |
|
270 nsTArray<PermissionRequest> permArray; |
|
271 nsTArray<nsString> emptyOptions; |
|
272 permArray.AppendElement(PermissionRequest( |
|
273 NS_LITERAL_CSTRING("desktop-notification"), |
|
274 NS_LITERAL_CSTRING("unused"), |
|
275 emptyOptions)); |
|
276 child->SendPContentPermissionRequestConstructor(this, permArray, |
|
277 IPC::Principal(mPrincipal)); |
|
278 |
|
279 Sendprompt(); |
|
280 return NS_OK; |
|
281 } |
|
282 |
|
283 nsCOMPtr<nsIContentPermissionPrompt> prompt = |
|
284 do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); |
|
285 if (prompt) { |
|
286 prompt->Prompt(this); |
|
287 } |
|
288 |
|
289 return NS_OK; |
|
290 } |
|
291 |
|
292 NS_IMETHODIMP |
|
293 NotificationPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal) |
|
294 { |
|
295 NS_ADDREF(*aRequestingPrincipal = mPrincipal); |
|
296 return NS_OK; |
|
297 } |
|
298 |
|
299 NS_IMETHODIMP |
|
300 NotificationPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow) |
|
301 { |
|
302 NS_ADDREF(*aRequestingWindow = mWindow); |
|
303 return NS_OK; |
|
304 } |
|
305 |
|
306 NS_IMETHODIMP |
|
307 NotificationPermissionRequest::GetElement(nsIDOMElement** aElement) |
|
308 { |
|
309 NS_ENSURE_ARG_POINTER(aElement); |
|
310 *aElement = nullptr; |
|
311 return NS_OK; |
|
312 } |
|
313 |
|
314 NS_IMETHODIMP |
|
315 NotificationPermissionRequest::Cancel() |
|
316 { |
|
317 mPermission = NotificationPermission::Denied; |
|
318 return DispatchCallback(); |
|
319 } |
|
320 |
|
321 NS_IMETHODIMP |
|
322 NotificationPermissionRequest::Allow(JS::HandleValue aChoices) |
|
323 { |
|
324 MOZ_ASSERT(aChoices.isUndefined()); |
|
325 |
|
326 mPermission = NotificationPermission::Granted; |
|
327 return DispatchCallback(); |
|
328 } |
|
329 |
|
330 inline nsresult |
|
331 NotificationPermissionRequest::DispatchCallback() |
|
332 { |
|
333 if (!mCallback) { |
|
334 return NS_OK; |
|
335 } |
|
336 |
|
337 nsCOMPtr<nsIRunnable> callbackRunnable = NS_NewRunnableMethod(this, |
|
338 &NotificationPermissionRequest::CallCallback); |
|
339 return NS_DispatchToMainThread(callbackRunnable); |
|
340 } |
|
341 |
|
342 nsresult |
|
343 NotificationPermissionRequest::CallCallback() |
|
344 { |
|
345 ErrorResult rv; |
|
346 mCallback->Call(mPermission, rv); |
|
347 return rv.ErrorCode(); |
|
348 } |
|
349 |
|
350 NS_IMETHODIMP |
|
351 NotificationPermissionRequest::GetTypes(nsIArray** aTypes) |
|
352 { |
|
353 nsTArray<nsString> emptyOptions; |
|
354 return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), |
|
355 NS_LITERAL_CSTRING("unused"), |
|
356 emptyOptions, |
|
357 aTypes); |
|
358 } |
|
359 |
|
360 bool |
|
361 NotificationPermissionRequest::Recv__delete__(const bool& aAllow, |
|
362 const InfallibleTArray<PermissionChoice>& choices) |
|
363 { |
|
364 MOZ_ASSERT(choices.IsEmpty(), "Notification doesn't support permission choice"); |
|
365 |
|
366 if (aAllow) { |
|
367 (void) Allow(JS::UndefinedHandleValue); |
|
368 } else { |
|
369 (void) Cancel(); |
|
370 } |
|
371 return true; |
|
372 } |
|
373 |
|
374 NS_IMPL_ISUPPORTS(NotificationTask, nsIRunnable) |
|
375 |
|
376 NS_IMETHODIMP |
|
377 NotificationTask::Run() |
|
378 { |
|
379 switch (mAction) { |
|
380 case eShow: |
|
381 mNotification->ShowInternal(); |
|
382 break; |
|
383 case eClose: |
|
384 mNotification->CloseInternal(); |
|
385 break; |
|
386 default: |
|
387 MOZ_CRASH("Unexpected action for NotificationTask."); |
|
388 } |
|
389 return NS_OK; |
|
390 } |
|
391 |
|
392 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver) |
|
393 |
|
394 NS_IMETHODIMP |
|
395 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, |
|
396 const char16_t* aData) |
|
397 { |
|
398 if (!strcmp("alertclickcallback", aTopic)) { |
|
399 nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner(); |
|
400 nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; |
|
401 if (doc) { |
|
402 nsContentUtils::DispatchChromeEvent(doc, window, |
|
403 NS_LITERAL_STRING("DOMWebNotificationClicked"), |
|
404 true, true); |
|
405 } |
|
406 mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("click")); |
|
407 } else if (!strcmp("alertfinished", aTopic)) { |
|
408 mNotification->mIsClosed = true; |
|
409 mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close")); |
|
410 } else if (!strcmp("alertshow", aTopic)) { |
|
411 mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show")); |
|
412 } |
|
413 |
|
414 return NS_OK; |
|
415 } |
|
416 |
|
417 Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody, |
|
418 NotificationDirection aDir, const nsAString& aLang, |
|
419 const nsAString& aTag, const nsAString& aIconUrl, |
|
420 nsPIDOMWindow* aWindow) |
|
421 : DOMEventTargetHelper(aWindow), |
|
422 mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang), |
|
423 mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false) |
|
424 { |
|
425 } |
|
426 |
|
427 // static |
|
428 already_AddRefed<Notification> |
|
429 Notification::Constructor(const GlobalObject& aGlobal, |
|
430 const nsAString& aTitle, |
|
431 const NotificationOptions& aOptions, |
|
432 ErrorResult& aRv) |
|
433 { |
|
434 MOZ_ASSERT(NS_IsMainThread()); |
|
435 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports()); |
|
436 MOZ_ASSERT(window, "Window should not be null."); |
|
437 nsRefPtr<Notification> notification = CreateInternal(window, |
|
438 EmptyString(), |
|
439 aTitle, |
|
440 aOptions); |
|
441 |
|
442 // Queue a task to show the notification. |
|
443 nsCOMPtr<nsIRunnable> showNotificationTask = |
|
444 new NotificationTask(notification, NotificationTask::eShow); |
|
445 NS_DispatchToCurrentThread(showNotificationTask); |
|
446 |
|
447 // Persist the notification. |
|
448 nsresult rv; |
|
449 nsCOMPtr<nsINotificationStorage> notificationStorage = |
|
450 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); |
|
451 if (NS_FAILED(rv)) { |
|
452 aRv.Throw(rv); |
|
453 return nullptr; |
|
454 } |
|
455 |
|
456 nsString origin; |
|
457 aRv = GetOrigin(window, origin); |
|
458 if (aRv.Failed()) { |
|
459 return nullptr; |
|
460 } |
|
461 |
|
462 nsString id; |
|
463 notification->GetID(id); |
|
464 aRv = notificationStorage->Put(origin, |
|
465 id, |
|
466 aTitle, |
|
467 DirectionToString(aOptions.mDir), |
|
468 aOptions.mLang, |
|
469 aOptions.mBody, |
|
470 aOptions.mTag, |
|
471 aOptions.mIcon); |
|
472 if (aRv.Failed()) { |
|
473 return nullptr; |
|
474 } |
|
475 |
|
476 return notification.forget(); |
|
477 } |
|
478 |
|
479 already_AddRefed<Notification> |
|
480 Notification::CreateInternal(nsPIDOMWindow* aWindow, |
|
481 const nsAString& aID, |
|
482 const nsAString& aTitle, |
|
483 const NotificationOptions& aOptions) |
|
484 { |
|
485 nsString id; |
|
486 if (!aID.IsEmpty()) { |
|
487 id = aID; |
|
488 } else { |
|
489 nsCOMPtr<nsIUUIDGenerator> uuidgen = |
|
490 do_GetService("@mozilla.org/uuid-generator;1"); |
|
491 NS_ENSURE_TRUE(uuidgen, nullptr); |
|
492 nsID uuid; |
|
493 nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid); |
|
494 NS_ENSURE_SUCCESS(rv, nullptr); |
|
495 |
|
496 char buffer[NSID_LENGTH]; |
|
497 uuid.ToProvidedString(buffer); |
|
498 NS_ConvertASCIItoUTF16 convertedID(buffer); |
|
499 id = convertedID; |
|
500 } |
|
501 |
|
502 nsRefPtr<Notification> notification = new Notification(id, |
|
503 aTitle, |
|
504 aOptions.mBody, |
|
505 aOptions.mDir, |
|
506 aOptions.mLang, |
|
507 aOptions.mTag, |
|
508 aOptions.mIcon, |
|
509 aWindow); |
|
510 return notification.forget(); |
|
511 } |
|
512 |
|
513 nsIPrincipal* |
|
514 Notification::GetPrincipal() |
|
515 { |
|
516 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); |
|
517 NS_ENSURE_TRUE(sop, nullptr); |
|
518 return sop->GetPrincipal(); |
|
519 } |
|
520 |
|
521 void |
|
522 Notification::ShowInternal() |
|
523 { |
|
524 nsCOMPtr<nsIAlertsService> alertService = |
|
525 do_GetService(NS_ALERTSERVICE_CONTRACTID); |
|
526 |
|
527 ErrorResult result; |
|
528 if (GetPermissionInternal(GetOwner(), result) != |
|
529 NotificationPermission::Granted || !alertService) { |
|
530 // We do not have permission to show a notification or alert service |
|
531 // is not available. |
|
532 DispatchTrustedEvent(NS_LITERAL_STRING("error")); |
|
533 return; |
|
534 } |
|
535 |
|
536 nsresult rv; |
|
537 nsAutoString absoluteUrl; |
|
538 if (mIconUrl.Length() > 0) { |
|
539 // Resolve image URL against document base URI. |
|
540 nsIDocument* doc = GetOwner()->GetExtantDoc(); |
|
541 if (doc) { |
|
542 nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI(); |
|
543 if (baseUri) { |
|
544 nsCOMPtr<nsIURI> srcUri; |
|
545 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri), |
|
546 mIconUrl, doc, baseUri); |
|
547 if (NS_SUCCEEDED(rv)) { |
|
548 nsAutoCString src; |
|
549 srcUri->GetSpec(src); |
|
550 absoluteUrl = NS_ConvertUTF8toUTF16(src); |
|
551 } |
|
552 } |
|
553 |
|
554 } |
|
555 } |
|
556 |
|
557 nsCOMPtr<nsIObserver> observer = new NotificationObserver(this); |
|
558 |
|
559 nsString alertName; |
|
560 rv = GetAlertName(alertName); |
|
561 NS_ENSURE_SUCCESS_VOID(rv); |
|
562 |
|
563 #ifdef MOZ_B2G |
|
564 nsCOMPtr<nsIAppNotificationService> appNotifier = |
|
565 do_GetService("@mozilla.org/system-alerts-service;1"); |
|
566 if (appNotifier) { |
|
567 nsCOMPtr<nsPIDOMWindow> window = GetOwner(); |
|
568 uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId(); |
|
569 |
|
570 if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { |
|
571 nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1"); |
|
572 nsString manifestUrl = EmptyString(); |
|
573 rv = appsService->GetManifestURLByLocalId(appId, manifestUrl); |
|
574 if (NS_SUCCEEDED(rv)) { |
|
575 mozilla::AutoSafeJSContext cx; |
|
576 JS::Rooted<JS::Value> val(cx); |
|
577 AppNotificationServiceOptions ops; |
|
578 ops.mTextClickable = true; |
|
579 ops.mManifestURL = manifestUrl; |
|
580 ops.mId = alertName; |
|
581 ops.mDbId = mID; |
|
582 ops.mDir = DirectionToString(mDir); |
|
583 ops.mLang = mLang; |
|
584 ops.mTag = mTag; |
|
585 |
|
586 if (!ops.ToObject(cx, &val)) { |
|
587 NS_WARNING("Converting dict to object failed!"); |
|
588 return; |
|
589 } |
|
590 |
|
591 appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody, |
|
592 observer, val); |
|
593 return; |
|
594 } |
|
595 } |
|
596 } |
|
597 #endif |
|
598 |
|
599 // In the case of IPC, the parent process uses the cookie to map to |
|
600 // nsIObserver. Thus the cookie must be unique to differentiate observers. |
|
601 nsString uniqueCookie = NS_LITERAL_STRING("notification:"); |
|
602 uniqueCookie.AppendInt(sCount++); |
|
603 alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true, |
|
604 uniqueCookie, observer, alertName, |
|
605 DirectionToString(mDir), mLang, |
|
606 GetPrincipal()); |
|
607 } |
|
608 |
|
609 void |
|
610 Notification::RequestPermission(const GlobalObject& aGlobal, |
|
611 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback, |
|
612 ErrorResult& aRv) |
|
613 { |
|
614 // Get principal from global to make permission request for notifications. |
|
615 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports()); |
|
616 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports()); |
|
617 if (!sop) { |
|
618 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
619 return; |
|
620 } |
|
621 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); |
|
622 |
|
623 NotificationPermissionCallback* permissionCallback = nullptr; |
|
624 if (aCallback.WasPassed()) { |
|
625 permissionCallback = &aCallback.Value(); |
|
626 } |
|
627 nsCOMPtr<nsIRunnable> request = |
|
628 new NotificationPermissionRequest(principal, window, permissionCallback); |
|
629 |
|
630 NS_DispatchToMainThread(request); |
|
631 } |
|
632 |
|
633 NotificationPermission |
|
634 Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv) |
|
635 { |
|
636 return GetPermissionInternal(aGlobal.GetAsSupports(), aRv); |
|
637 } |
|
638 |
|
639 NotificationPermission |
|
640 Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) |
|
641 { |
|
642 // Get principal from global to check permission for notifications. |
|
643 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal); |
|
644 if (!sop) { |
|
645 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
646 return NotificationPermission::Denied; |
|
647 } |
|
648 |
|
649 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); |
|
650 if (nsContentUtils::IsSystemPrincipal(principal)) { |
|
651 return NotificationPermission::Granted; |
|
652 } else { |
|
653 // Allow files to show notifications by default. |
|
654 nsCOMPtr<nsIURI> uri; |
|
655 principal->GetURI(getter_AddRefs(uri)); |
|
656 if (uri) { |
|
657 bool isFile; |
|
658 uri->SchemeIs("file", &isFile); |
|
659 if (isFile) { |
|
660 return NotificationPermission::Granted; |
|
661 } |
|
662 } |
|
663 } |
|
664 |
|
665 // We also allow notifications is they are pref'ed on. |
|
666 if (Preferences::GetBool("notification.prompt.testing", false)) { |
|
667 if (Preferences::GetBool("notification.prompt.testing.allow", true)) { |
|
668 return NotificationPermission::Granted; |
|
669 } else { |
|
670 return NotificationPermission::Denied; |
|
671 } |
|
672 } |
|
673 |
|
674 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; |
|
675 |
|
676 nsCOMPtr<nsIPermissionManager> permissionManager = |
|
677 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); |
|
678 |
|
679 permissionManager->TestPermissionFromPrincipal(principal, |
|
680 "desktop-notification", |
|
681 &permission); |
|
682 |
|
683 // Convert the result to one of the enum types. |
|
684 switch (permission) { |
|
685 case nsIPermissionManager::ALLOW_ACTION: |
|
686 return NotificationPermission::Granted; |
|
687 case nsIPermissionManager::DENY_ACTION: |
|
688 return NotificationPermission::Denied; |
|
689 default: |
|
690 return NotificationPermission::Default; |
|
691 } |
|
692 } |
|
693 |
|
694 already_AddRefed<Promise> |
|
695 Notification::Get(const GlobalObject& aGlobal, |
|
696 const GetNotificationOptions& aFilter, |
|
697 ErrorResult& aRv) |
|
698 { |
|
699 nsCOMPtr<nsIGlobalObject> global = |
|
700 do_QueryInterface(aGlobal.GetAsSupports()); |
|
701 MOZ_ASSERT(global); |
|
702 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global); |
|
703 MOZ_ASSERT(window); |
|
704 nsIDocument* doc = window->GetExtantDoc(); |
|
705 if (!doc) { |
|
706 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
707 return nullptr; |
|
708 } |
|
709 |
|
710 nsString origin; |
|
711 aRv = GetOrigin(window, origin); |
|
712 if (aRv.Failed()) { |
|
713 return nullptr; |
|
714 } |
|
715 |
|
716 nsresult rv; |
|
717 nsCOMPtr<nsINotificationStorage> notificationStorage = |
|
718 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); |
|
719 if (NS_FAILED(rv)) { |
|
720 aRv.Throw(rv); |
|
721 return nullptr; |
|
722 } |
|
723 |
|
724 nsRefPtr<Promise> promise = new Promise(global); |
|
725 nsCOMPtr<nsINotificationStorageCallback> callback = |
|
726 new NotificationStorageCallback(aGlobal, window, promise); |
|
727 nsString tag = aFilter.mTag.WasPassed() ? |
|
728 aFilter.mTag.Value() : |
|
729 EmptyString(); |
|
730 aRv = notificationStorage->Get(origin, tag, callback); |
|
731 if (aRv.Failed()) { |
|
732 return nullptr; |
|
733 } |
|
734 |
|
735 return promise.forget(); |
|
736 } |
|
737 |
|
738 JSObject* |
|
739 Notification::WrapObject(JSContext* aCx) |
|
740 { |
|
741 return mozilla::dom::NotificationBinding::Wrap(aCx, this); |
|
742 } |
|
743 |
|
744 void |
|
745 Notification::Close() |
|
746 { |
|
747 // Queue a task to close the notification. |
|
748 nsCOMPtr<nsIRunnable> closeNotificationTask = |
|
749 new NotificationTask(this, NotificationTask::eClose); |
|
750 NS_DispatchToMainThread(closeNotificationTask); |
|
751 } |
|
752 |
|
753 void |
|
754 Notification::CloseInternal() |
|
755 { |
|
756 if (!mIsClosed) { |
|
757 nsresult rv; |
|
758 // Don't bail out if notification storage fails, since we still |
|
759 // want to send the close event through the alert service. |
|
760 nsCOMPtr<nsINotificationStorage> notificationStorage = |
|
761 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); |
|
762 if (notificationStorage) { |
|
763 nsString origin; |
|
764 rv = GetOrigin(GetOwner(), origin); |
|
765 if (NS_SUCCEEDED(rv)) { |
|
766 notificationStorage->Delete(origin, mID); |
|
767 } |
|
768 } |
|
769 |
|
770 nsCOMPtr<nsIAlertsService> alertService = |
|
771 do_GetService(NS_ALERTSERVICE_CONTRACTID); |
|
772 if (alertService) { |
|
773 nsString alertName; |
|
774 rv = GetAlertName(alertName); |
|
775 if (NS_SUCCEEDED(rv)) { |
|
776 alertService->CloseAlert(alertName, GetPrincipal()); |
|
777 } |
|
778 } |
|
779 } |
|
780 } |
|
781 |
|
782 nsresult |
|
783 Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin) |
|
784 { |
|
785 if (!aWindow) { |
|
786 return NS_ERROR_FAILURE; |
|
787 } |
|
788 nsresult rv; |
|
789 nsIDocument* doc = aWindow->GetExtantDoc(); |
|
790 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); |
|
791 nsIPrincipal* principal = doc->NodePrincipal(); |
|
792 NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED); |
|
793 |
|
794 uint16_t appStatus = principal->GetAppStatus(); |
|
795 uint32_t appId = principal->GetAppId(); |
|
796 |
|
797 if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED || |
|
798 appId == nsIScriptSecurityManager::NO_APP_ID || |
|
799 appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { |
|
800 rv = nsContentUtils::GetUTFOrigin(principal, aOrigin); |
|
801 NS_ENSURE_SUCCESS(rv, rv); |
|
802 } else { |
|
803 // If we are in "app code", use manifest URL as unique origin since |
|
804 // multiple apps can share the same origin but not same notifications. |
|
805 nsCOMPtr<nsIAppsService> appsService = |
|
806 do_GetService("@mozilla.org/AppsService;1", &rv); |
|
807 NS_ENSURE_SUCCESS(rv, rv); |
|
808 appsService->GetManifestURLByLocalId(appId, aOrigin); |
|
809 } |
|
810 |
|
811 return NS_OK; |
|
812 } |
|
813 |
|
814 nsresult |
|
815 Notification::GetAlertName(nsString& aAlertName) |
|
816 { |
|
817 // Get the notification name that is unique per origin + tag/ID. |
|
818 // The name of the alert is of the form origin#tag/ID. |
|
819 nsresult rv = GetOrigin(GetOwner(), aAlertName); |
|
820 NS_ENSURE_SUCCESS(rv, rv); |
|
821 aAlertName.AppendLiteral("#"); |
|
822 if (!mTag.IsEmpty()) { |
|
823 aAlertName.Append(NS_LITERAL_STRING("tag:")); |
|
824 aAlertName.Append(mTag); |
|
825 } else { |
|
826 aAlertName.Append(NS_LITERAL_STRING("notag:")); |
|
827 aAlertName.Append(mID); |
|
828 } |
|
829 return NS_OK; |
|
830 } |
|
831 |
|
832 } // namespace dom |
|
833 } // namespace mozilla |
|
834 |