Line data Source code
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 "PushNotifier.h"
6 :
7 : #include "nsContentUtils.h"
8 : #include "nsCOMPtr.h"
9 : #include "nsICategoryManager.h"
10 : #include "nsIXULRuntime.h"
11 : #include "nsNetUtil.h"
12 : #include "nsXPCOM.h"
13 : #include "ServiceWorkerManager.h"
14 :
15 : #include "mozilla/Services.h"
16 : #include "mozilla/Unused.h"
17 :
18 : #include "mozilla/dom/BodyUtil.h"
19 : #include "mozilla/dom/ContentChild.h"
20 : #include "mozilla/dom/ContentParent.h"
21 :
22 : namespace mozilla {
23 : namespace dom {
24 :
25 : using workers::AssertIsOnMainThread;
26 : using workers::ServiceWorkerManager;
27 :
28 2 : PushNotifier::PushNotifier()
29 2 : {}
30 :
31 0 : PushNotifier::~PushNotifier()
32 0 : {}
33 :
34 0 : NS_IMPL_CYCLE_COLLECTION_0(PushNotifier)
35 :
36 9 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier)
37 7 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushNotifier)
38 2 : NS_INTERFACE_MAP_ENTRY(nsIPushNotifier)
39 2 : NS_INTERFACE_MAP_END
40 :
41 9 : NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier)
42 7 : NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
43 :
44 : NS_IMETHODIMP
45 0 : PushNotifier::NotifyPushWithData(const nsACString& aScope,
46 : nsIPrincipal* aPrincipal,
47 : const nsAString& aMessageId,
48 : uint32_t aDataLen, uint8_t* aData)
49 : {
50 0 : NS_ENSURE_ARG(aPrincipal);
51 0 : nsTArray<uint8_t> data;
52 0 : if (!data.SetCapacity(aDataLen, fallible)) {
53 0 : return NS_ERROR_OUT_OF_MEMORY;
54 : }
55 0 : if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) {
56 0 : return NS_ERROR_OUT_OF_MEMORY;
57 : }
58 0 : PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(data));
59 0 : return Dispatch(dispatcher);
60 : }
61 :
62 : NS_IMETHODIMP
63 0 : PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
64 : const nsAString& aMessageId)
65 : {
66 0 : NS_ENSURE_ARG(aPrincipal);
67 0 : PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
68 0 : return Dispatch(dispatcher);
69 : }
70 :
71 : NS_IMETHODIMP
72 0 : PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
73 : nsIPrincipal* aPrincipal)
74 : {
75 0 : NS_ENSURE_ARG(aPrincipal);
76 0 : PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal);
77 0 : return Dispatch(dispatcher);
78 : }
79 :
80 : NS_IMETHODIMP
81 0 : PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
82 : nsIPrincipal* aPrincipal)
83 : {
84 0 : NS_ENSURE_ARG(aPrincipal);
85 0 : PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
86 0 : return Dispatch(dispatcher);
87 : }
88 :
89 : NS_IMETHODIMP
90 0 : PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
91 : const nsAString& aMessage, uint32_t aFlags)
92 : {
93 0 : NS_ENSURE_ARG(aPrincipal);
94 0 : PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags);
95 0 : return Dispatch(dispatcher);
96 : }
97 :
98 : nsresult
99 0 : PushNotifier::Dispatch(PushDispatcher& aDispatcher)
100 : {
101 0 : if (XRE_IsParentProcess()) {
102 : // Always notify XPCOM observers in the parent process.
103 0 : Unused << NS_WARN_IF(NS_FAILED(aDispatcher.NotifyObservers()));
104 :
105 0 : nsTArray<ContentParent*> contentActors;
106 0 : ContentParent::GetAll(contentActors);
107 0 : if (!contentActors.IsEmpty()) {
108 : // At least one content process is active, so e10s must be enabled.
109 : // Broadcast a message to notify observers and service workers.
110 0 : for (uint32_t i = 0; i < contentActors.Length(); ++i) {
111 : // Ensure that the content actor has the permissions avaliable for the
112 : // principal the push is being sent for before sending the push message
113 : // down.
114 0 : Unused << contentActors[i]->
115 0 : TransmitPermissionsForPrincipal(aDispatcher.GetPrincipal());
116 0 : if (aDispatcher.SendToChild(contentActors[i])) {
117 : // Only send the push message to the first content process to avoid
118 : // multiple SWs showing the same notification. See bug 1300112.
119 0 : break;
120 : }
121 : }
122 0 : return NS_OK;
123 : }
124 :
125 0 : if (BrowserTabsRemoteAutostart()) {
126 : // e10s is enabled, but no content processes are active.
127 0 : return aDispatcher.HandleNoChildProcesses();
128 : }
129 :
130 : // e10s is disabled; notify workers in the parent.
131 0 : return aDispatcher.NotifyWorkers();
132 : }
133 :
134 : // Otherwise, we're in the content process, so e10s must be enabled. Notify
135 : // observers and workers, then send a message to notify observers in the
136 : // parent.
137 0 : MOZ_ASSERT(XRE_IsContentProcess());
138 :
139 0 : nsresult rv = aDispatcher.NotifyObserversAndWorkers();
140 :
141 0 : ContentChild* parentActor = ContentChild::GetSingleton();
142 0 : if (!NS_WARN_IF(!parentActor)) {
143 0 : Unused << NS_WARN_IF(!aDispatcher.SendToParent(parentActor));
144 : }
145 :
146 0 : return rv;
147 : }
148 :
149 0 : PushData::PushData(const nsTArray<uint8_t>& aData)
150 0 : : mData(aData)
151 0 : {}
152 :
153 0 : PushData::~PushData()
154 0 : {}
155 :
156 0 : NS_IMPL_CYCLE_COLLECTION_0(PushData)
157 :
158 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData)
159 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData)
160 0 : NS_INTERFACE_MAP_ENTRY(nsIPushData)
161 0 : NS_INTERFACE_MAP_END
162 :
163 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData)
164 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData)
165 :
166 : nsresult
167 0 : PushData::EnsureDecodedText()
168 : {
169 0 : if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
170 0 : return NS_OK;
171 : }
172 0 : nsresult rv = BodyUtil::ConsumeText(
173 0 : mData.Length(),
174 : reinterpret_cast<uint8_t*>(mData.Elements()),
175 : mDecodedText
176 0 : );
177 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
178 0 : mDecodedText.Truncate();
179 0 : return rv;
180 : }
181 0 : return NS_OK;
182 : }
183 :
184 : NS_IMETHODIMP
185 0 : PushData::Text(nsAString& aText)
186 : {
187 0 : nsresult rv = EnsureDecodedText();
188 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
189 0 : return rv;
190 : }
191 0 : aText = mDecodedText;
192 0 : return NS_OK;
193 : }
194 :
195 : NS_IMETHODIMP
196 0 : PushData::Json(JSContext* aCx,
197 : JS::MutableHandle<JS::Value> aResult)
198 : {
199 0 : nsresult rv = EnsureDecodedText();
200 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
201 0 : return rv;
202 : }
203 0 : ErrorResult error;
204 0 : BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
205 0 : return error.StealNSResult();
206 : }
207 :
208 : NS_IMETHODIMP
209 0 : PushData::Binary(uint32_t* aDataLen, uint8_t** aData)
210 : {
211 0 : NS_ENSURE_ARG_POINTER(aDataLen);
212 0 : NS_ENSURE_ARG_POINTER(aData);
213 :
214 0 : *aData = nullptr;
215 0 : if (mData.IsEmpty()) {
216 0 : *aDataLen = 0;
217 0 : return NS_OK;
218 : }
219 0 : uint32_t length = mData.Length();
220 0 : uint8_t* data = static_cast<uint8_t*>(NS_Alloc(length * sizeof(uint8_t)));
221 0 : if (!data) {
222 0 : return NS_ERROR_OUT_OF_MEMORY;
223 : }
224 0 : memcpy(data, mData.Elements(), length * sizeof(uint8_t));
225 0 : *aDataLen = length;
226 0 : *aData = data;
227 0 : return NS_OK;
228 : }
229 :
230 0 : PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
231 : : mPrincipal(aPrincipal)
232 0 : , mData(aData)
233 0 : {}
234 :
235 0 : PushMessage::~PushMessage()
236 0 : {}
237 :
238 0 : NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData)
239 :
240 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
241 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
242 0 : NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
243 0 : NS_INTERFACE_MAP_END
244 :
245 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
246 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
247 :
248 : NS_IMETHODIMP
249 0 : PushMessage::GetPrincipal(nsIPrincipal** aPrincipal)
250 : {
251 0 : NS_ENSURE_ARG_POINTER(aPrincipal);
252 :
253 0 : nsCOMPtr<nsIPrincipal> principal = mPrincipal;
254 0 : principal.forget(aPrincipal);
255 0 : return NS_OK;
256 : }
257 :
258 : NS_IMETHODIMP
259 0 : PushMessage::GetData(nsIPushData** aData)
260 : {
261 0 : NS_ENSURE_ARG_POINTER(aData);
262 :
263 0 : nsCOMPtr<nsIPushData> data = mData;
264 0 : data.forget(aData);
265 0 : return NS_OK;
266 : }
267 :
268 0 : PushDispatcher::PushDispatcher(const nsACString& aScope,
269 0 : nsIPrincipal* aPrincipal)
270 : : mScope(aScope)
271 0 : , mPrincipal(aPrincipal)
272 0 : {}
273 :
274 0 : PushDispatcher::~PushDispatcher()
275 0 : {}
276 :
277 : nsresult
278 0 : PushDispatcher::HandleNoChildProcesses()
279 : {
280 0 : return NS_OK;
281 : }
282 :
283 : nsresult
284 0 : PushDispatcher::NotifyObserversAndWorkers()
285 : {
286 0 : Unused << NS_WARN_IF(NS_FAILED(NotifyObservers()));
287 0 : return NotifyWorkers();
288 : }
289 :
290 : bool
291 0 : PushDispatcher::ShouldNotifyWorkers()
292 : {
293 0 : if (NS_WARN_IF(!mPrincipal)) {
294 0 : return false;
295 : }
296 : // System subscriptions use observer notifications instead of service worker
297 : // events. The `testing.notifyWorkers` pref disables worker events for
298 : // non-system subscriptions.
299 0 : return !nsContentUtils::IsSystemPrincipal(mPrincipal) &&
300 0 : Preferences::GetBool("dom.push.testing.notifyWorkers", true);
301 : }
302 :
303 : nsresult
304 0 : PushDispatcher::DoNotifyObservers(nsISupports *aSubject, const char *aTopic,
305 : const nsACString& aScope)
306 : {
307 : nsCOMPtr<nsIObserverService> obsService =
308 0 : mozilla::services::GetObserverService();
309 0 : if (!obsService) {
310 0 : return NS_ERROR_FAILURE;
311 : }
312 : // If there's a service for this push category, make sure it is alive.
313 : nsCOMPtr<nsICategoryManager> catMan =
314 0 : do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
315 0 : if (catMan) {
316 0 : nsXPIDLCString contractId;
317 0 : nsresult rv = catMan->GetCategoryEntry("push",
318 : mScope.BeginReading(),
319 0 : getter_Copies(contractId));
320 0 : if (NS_SUCCEEDED(rv)) {
321 : // Ensure the service is created - we don't need to do anything with
322 : // it though - we assume the service constructor attaches a listener.
323 0 : nsCOMPtr<nsISupports> service = do_GetService(contractId);
324 : }
325 : }
326 0 : return obsService->NotifyObservers(aSubject, aTopic,
327 0 : NS_ConvertUTF8toUTF16(mScope).get());
328 : }
329 :
330 0 : PushMessageDispatcher::PushMessageDispatcher(const nsACString& aScope,
331 : nsIPrincipal* aPrincipal,
332 : const nsAString& aMessageId,
333 0 : const Maybe<nsTArray<uint8_t>>& aData)
334 : : PushDispatcher(aScope, aPrincipal)
335 : , mMessageId(aMessageId)
336 0 : , mData(aData)
337 0 : {}
338 :
339 0 : PushMessageDispatcher::~PushMessageDispatcher()
340 0 : {}
341 :
342 : nsresult
343 0 : PushMessageDispatcher::NotifyObservers()
344 : {
345 0 : nsCOMPtr<nsIPushData> data;
346 0 : if (mData) {
347 0 : data = new PushData(mData.ref());
348 : }
349 0 : nsCOMPtr<nsIPushMessage> message = new PushMessage(mPrincipal, data);
350 0 : return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, mScope);
351 : }
352 :
353 : nsresult
354 0 : PushMessageDispatcher::NotifyWorkers()
355 : {
356 0 : if (!ShouldNotifyWorkers()) {
357 0 : return NS_OK;
358 : }
359 0 : RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
360 0 : if (!swm) {
361 0 : return NS_ERROR_FAILURE;
362 : }
363 0 : nsAutoCString originSuffix;
364 0 : nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
365 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
366 0 : return rv;
367 : }
368 0 : return swm->SendPushEvent(originSuffix, mScope, mMessageId, mData);
369 : }
370 :
371 : bool
372 0 : PushMessageDispatcher::SendToParent(ContentChild* aParentActor)
373 : {
374 0 : if (mData) {
375 0 : return aParentActor->SendNotifyPushObserversWithData(mScope,
376 0 : IPC::Principal(mPrincipal),
377 : mMessageId,
378 0 : mData.ref());
379 : }
380 0 : return aParentActor->SendNotifyPushObservers(mScope,
381 0 : IPC::Principal(mPrincipal),
382 0 : mMessageId);
383 : }
384 :
385 : bool
386 0 : PushMessageDispatcher::SendToChild(ContentParent* aContentActor)
387 : {
388 0 : if (mData) {
389 0 : return aContentActor->SendPushWithData(mScope, IPC::Principal(mPrincipal),
390 0 : mMessageId, mData.ref());
391 : }
392 0 : return aContentActor->SendPush(mScope, IPC::Principal(mPrincipal),
393 0 : mMessageId);
394 : }
395 :
396 0 : PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(const nsACString& aScope,
397 0 : nsIPrincipal* aPrincipal)
398 0 : : PushDispatcher(aScope, aPrincipal)
399 0 : {}
400 :
401 0 : PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher()
402 0 : {}
403 :
404 : nsresult
405 0 : PushSubscriptionChangeDispatcher::NotifyObservers()
406 : {
407 0 : return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
408 0 : mScope);
409 : }
410 :
411 : nsresult
412 0 : PushSubscriptionChangeDispatcher::NotifyWorkers()
413 : {
414 0 : if (!ShouldNotifyWorkers()) {
415 0 : return NS_OK;
416 : }
417 0 : RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
418 0 : if (!swm) {
419 0 : return NS_ERROR_FAILURE;
420 : }
421 0 : nsAutoCString originSuffix;
422 0 : nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
423 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
424 0 : return rv;
425 : }
426 0 : return swm->SendPushSubscriptionChangeEvent(originSuffix, mScope);
427 : }
428 :
429 : bool
430 0 : PushSubscriptionChangeDispatcher::SendToParent(ContentChild* aParentActor)
431 : {
432 0 : return aParentActor->SendNotifyPushSubscriptionChangeObservers(mScope,
433 0 : IPC::Principal(mPrincipal));
434 : }
435 :
436 : bool
437 0 : PushSubscriptionChangeDispatcher::SendToChild(ContentParent* aContentActor)
438 : {
439 0 : return aContentActor->SendPushSubscriptionChange(mScope,
440 0 : IPC::Principal(mPrincipal));
441 : }
442 :
443 0 : PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(const nsACString& aScope,
444 0 : nsIPrincipal* aPrincipal)
445 0 : : PushDispatcher(aScope, aPrincipal)
446 0 : {}
447 :
448 0 : PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher()
449 0 : {}
450 :
451 : nsresult
452 0 : PushSubscriptionModifiedDispatcher::NotifyObservers()
453 : {
454 0 : return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
455 0 : mScope);
456 : }
457 :
458 : nsresult
459 0 : PushSubscriptionModifiedDispatcher::NotifyWorkers()
460 : {
461 0 : return NS_OK;
462 : }
463 :
464 : bool
465 0 : PushSubscriptionModifiedDispatcher::SendToParent(ContentChild* aParentActor)
466 : {
467 0 : return aParentActor->SendNotifyPushSubscriptionModifiedObservers(mScope,
468 0 : IPC::Principal(mPrincipal));
469 : }
470 :
471 : bool
472 0 : PushSubscriptionModifiedDispatcher::SendToChild(ContentParent* aContentActor)
473 : {
474 0 : return aContentActor->SendNotifyPushSubscriptionModifiedObservers(mScope,
475 0 : IPC::Principal(mPrincipal));
476 : }
477 :
478 0 : PushErrorDispatcher::PushErrorDispatcher(const nsACString& aScope,
479 : nsIPrincipal* aPrincipal,
480 : const nsAString& aMessage,
481 0 : uint32_t aFlags)
482 : : PushDispatcher(aScope, aPrincipal)
483 : , mMessage(aMessage)
484 0 : , mFlags(aFlags)
485 0 : {}
486 :
487 0 : PushErrorDispatcher::~PushErrorDispatcher()
488 0 : {}
489 :
490 : nsresult
491 0 : PushErrorDispatcher::NotifyObservers()
492 : {
493 0 : return NS_OK;
494 : }
495 :
496 : nsresult
497 0 : PushErrorDispatcher::NotifyWorkers()
498 : {
499 0 : if (!ShouldNotifyWorkers()) {
500 : // For system subscriptions, log the error directly to the browser console.
501 0 : return nsContentUtils::ReportToConsoleNonLocalized(mMessage,
502 : mFlags,
503 0 : NS_LITERAL_CSTRING("Push"),
504 : nullptr, /* aDocument */
505 : nullptr, /* aURI */
506 : EmptyString(), /* aLine */
507 : 0, /* aLineNumber */
508 : 0, /* aColumnNumber */
509 0 : nsContentUtils::eOMIT_LOCATION);
510 : }
511 : // For service worker subscriptions, report the error to all clients.
512 0 : RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
513 0 : if (swm) {
514 0 : swm->ReportToAllClients(mScope,
515 : mMessage,
516 0 : NS_ConvertUTF8toUTF16(mScope), /* aFilename */
517 : EmptyString(), /* aLine */
518 : 0, /* aLineNumber */
519 : 0, /* aColumnNumber */
520 0 : mFlags);
521 : }
522 0 : return NS_OK;
523 : }
524 :
525 : bool
526 0 : PushErrorDispatcher::SendToParent(ContentChild*)
527 : {
528 0 : return true;
529 : }
530 :
531 : bool
532 0 : PushErrorDispatcher::SendToChild(ContentParent* aContentActor)
533 : {
534 0 : return aContentActor->SendPushError(mScope, IPC::Principal(mPrincipal),
535 0 : mMessage, mFlags);
536 : }
537 :
538 : nsresult
539 0 : PushErrorDispatcher::HandleNoChildProcesses()
540 : {
541 : // Report to the console if no content processes are active.
542 0 : nsCOMPtr<nsIURI> scopeURI;
543 0 : nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope);
544 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
545 0 : return rv;
546 : }
547 0 : return nsContentUtils::ReportToConsoleNonLocalized(mMessage,
548 : mFlags,
549 0 : NS_LITERAL_CSTRING("Push"),
550 : nullptr, /* aDocument */
551 : scopeURI, /* aURI */
552 : EmptyString(), /* aLine */
553 : 0, /* aLineNumber */
554 : 0, /* aColumnNumber */
555 0 : nsContentUtils::eOMIT_LOCATION);
556 : }
557 :
558 : } // namespace dom
559 : } // namespace mozilla
|