|
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 |