1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PushNotifier.h"
9 #include "nsContentUtils.h"
11 #include "nsICategoryManager.h"
12 #include "nsIXULRuntime.h"
13 #include "nsNetUtil.h"
15 #include "mozilla/dom/ServiceWorkerManager.h"
16 #include "mozilla/BasePrincipal.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/Unused.h"
21 #include "mozilla/dom/BodyUtil.h"
22 #include "mozilla/dom/ContentChild.h"
23 #include "mozilla/dom/ContentParent.h"
28 PushNotifier::PushNotifier() = default;
30 PushNotifier::~PushNotifier() = default;
32 NS_IMPL_CYCLE_COLLECTION_0(PushNotifier
)
34 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier
)
35 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushNotifier
)
36 NS_INTERFACE_MAP_ENTRY(nsIPushNotifier
)
39 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier
)
40 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier
)
43 PushNotifier::NotifyPushWithData(const nsACString
& aScope
,
44 nsIPrincipal
* aPrincipal
,
45 const nsAString
& aMessageId
,
46 const nsTArray
<uint8_t>& aData
) {
47 NS_ENSURE_ARG(aPrincipal
);
48 // We still need to do this copying business, if we want the copy to be
49 // fallible. Just passing Some(aData) would do an infallible copy at the
50 // point where the Some() call happens.
51 nsTArray
<uint8_t> data
;
52 if (!data
.AppendElements(aData
, fallible
)) {
53 return NS_ERROR_OUT_OF_MEMORY
;
55 PushMessageDispatcher
dispatcher(aScope
, aPrincipal
, aMessageId
,
56 Some(std::move(data
)));
57 return Dispatch(dispatcher
);
61 PushNotifier::NotifyPush(const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
62 const nsAString
& aMessageId
) {
63 NS_ENSURE_ARG(aPrincipal
);
64 PushMessageDispatcher
dispatcher(aScope
, aPrincipal
, aMessageId
, Nothing());
65 return Dispatch(dispatcher
);
69 PushNotifier::NotifySubscriptionChange(const nsACString
& aScope
,
70 nsIPrincipal
* aPrincipal
) {
71 NS_ENSURE_ARG(aPrincipal
);
72 PushSubscriptionChangeDispatcher
dispatcher(aScope
, aPrincipal
);
73 return Dispatch(dispatcher
);
77 PushNotifier::NotifySubscriptionModified(const nsACString
& aScope
,
78 nsIPrincipal
* aPrincipal
) {
79 NS_ENSURE_ARG(aPrincipal
);
80 PushSubscriptionModifiedDispatcher
dispatcher(aScope
, aPrincipal
);
81 return Dispatch(dispatcher
);
85 PushNotifier::NotifyError(const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
86 const nsAString
& aMessage
, uint32_t aFlags
) {
87 NS_ENSURE_ARG(aPrincipal
);
88 PushErrorDispatcher
dispatcher(aScope
, aPrincipal
, aMessage
, aFlags
);
89 return Dispatch(dispatcher
);
92 nsresult
PushNotifier::Dispatch(PushDispatcher
& aDispatcher
) {
93 if (XRE_IsParentProcess()) {
94 // Always notify XPCOM observers in the parent process.
95 Unused
<< NS_WARN_IF(NS_FAILED(aDispatcher
.NotifyObservers()));
97 nsTArray
<ContentParent
*> contentActors
;
98 ContentParent::GetAll(contentActors
);
99 if (!contentActors
.IsEmpty() && !ServiceWorkerParentInterceptEnabled()) {
100 // At least one content process is active, so e10s must be enabled.
101 // Broadcast a message to notify observers and service workers.
102 for (uint32_t i
= 0; i
< contentActors
.Length(); ++i
) {
103 // We need to filter based on process type, only "web" AKA the default
104 // remote type is acceptable. This should not run when Fission is
105 // enabled, and we specifically don't want this for
106 // LARGE_ALLOCATION_REMOTE_TYPE, so don't use IsWebRemoteType().
107 if (contentActors
[i
]->GetRemoteType() != DEFAULT_REMOTE_TYPE
) {
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
114 Unused
<< contentActors
[i
]->TransmitPermissionsForPrincipal(
115 aDispatcher
.GetPrincipal());
116 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.
125 if (BrowserTabsRemoteAutostart() &&
126 !ServiceWorkerParentInterceptEnabled()) {
127 // e10s is enabled, but no content processes are active.
128 return aDispatcher
.HandleNoChildProcesses();
131 // e10s is disabled; notify workers in the parent.
132 return aDispatcher
.NotifyWorkers();
135 // Otherwise, we're in the content process, so e10s must be enabled. Notify
136 // observers and workers, then send a message to notify observers in the
138 MOZ_ASSERT(XRE_IsContentProcess());
140 nsresult rv
= aDispatcher
.NotifyObserversAndWorkers();
142 ContentChild
* parentActor
= ContentChild::GetSingleton();
143 if (!NS_WARN_IF(!parentActor
)) {
144 Unused
<< NS_WARN_IF(!aDispatcher
.SendToParent(parentActor
));
150 PushData::PushData(const nsTArray
<uint8_t>& aData
) : mData(aData
.Clone()) {}
152 PushData::~PushData() = default;
154 NS_IMPL_CYCLE_COLLECTION_0(PushData
)
156 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData
)
157 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushData
)
158 NS_INTERFACE_MAP_ENTRY(nsIPushData
)
161 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData
)
162 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData
)
164 nsresult
PushData::EnsureDecodedText() {
165 if (mData
.IsEmpty() || !mDecodedText
.IsEmpty()) {
168 nsresult rv
= BodyUtil::ConsumeText(
169 mData
.Length(), reinterpret_cast<uint8_t*>(mData
.Elements()),
171 if (NS_WARN_IF(NS_FAILED(rv
))) {
172 mDecodedText
.Truncate();
179 PushData::Text(nsAString
& aText
) {
180 nsresult rv
= EnsureDecodedText();
181 if (NS_WARN_IF(NS_FAILED(rv
))) {
184 aText
= mDecodedText
;
189 PushData::Json(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
) {
190 nsresult rv
= EnsureDecodedText();
191 if (NS_WARN_IF(NS_FAILED(rv
))) {
195 BodyUtil::ConsumeJson(aCx
, aResult
, mDecodedText
, error
);
196 return error
.StealNSResult();
200 PushData::Binary(nsTArray
<uint8_t>& aData
) {
201 aData
= mData
.Clone();
205 PushMessage::PushMessage(nsIPrincipal
* aPrincipal
, nsIPushData
* aData
)
206 : mPrincipal(aPrincipal
), mData(aData
) {}
208 PushMessage::~PushMessage() = default;
210 NS_IMPL_CYCLE_COLLECTION(PushMessage
, mPrincipal
, mData
)
212 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage
)
213 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushMessage
)
214 NS_INTERFACE_MAP_ENTRY(nsIPushMessage
)
217 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage
)
218 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage
)
221 PushMessage::GetPrincipal(nsIPrincipal
** aPrincipal
) {
222 NS_ENSURE_ARG_POINTER(aPrincipal
);
224 nsCOMPtr
<nsIPrincipal
> principal
= mPrincipal
;
225 principal
.forget(aPrincipal
);
230 PushMessage::GetData(nsIPushData
** aData
) {
231 NS_ENSURE_ARG_POINTER(aData
);
233 nsCOMPtr
<nsIPushData
> data
= mData
;
238 PushDispatcher::PushDispatcher(const nsACString
& aScope
,
239 nsIPrincipal
* aPrincipal
)
240 : mScope(aScope
), mPrincipal(aPrincipal
) {}
242 PushDispatcher::~PushDispatcher() = default;
244 nsresult
PushDispatcher::HandleNoChildProcesses() { return NS_OK
; }
246 nsresult
PushDispatcher::NotifyObserversAndWorkers() {
247 Unused
<< NS_WARN_IF(NS_FAILED(NotifyObservers()));
248 return NotifyWorkers();
251 bool PushDispatcher::ShouldNotifyWorkers() {
252 if (NS_WARN_IF(!mPrincipal
)) {
256 // System subscriptions use observer notifications instead of service worker
257 // events. The `testing.notifyWorkers` pref disables worker events for
258 // non-system subscriptions.
259 if (mPrincipal
->IsSystemPrincipal() ||
260 !Preferences::GetBool("dom.push.testing.notifyWorkers", true)) {
264 // If e10s is off, no need to worry about processes.
265 if (!BrowserTabsRemoteAutostart()) {
269 // If parent intercept is enabled, then we only want to notify in the parent
270 // process. Otherwise, we only want to notify in the child process.
271 bool isContentProcess
= XRE_GetProcessType() == GeckoProcessType_Content
;
272 bool parentInterceptEnabled
= ServiceWorkerParentInterceptEnabled();
273 if (parentInterceptEnabled
) {
274 return !isContentProcess
;
277 return isContentProcess
;
280 nsresult
PushDispatcher::DoNotifyObservers(nsISupports
* aSubject
,
282 const nsACString
& aScope
) {
283 nsCOMPtr
<nsIObserverService
> obsService
=
284 mozilla::services::GetObserverService();
286 return NS_ERROR_FAILURE
;
288 // If there's a service for this push category, make sure it is alive.
289 nsCOMPtr
<nsICategoryManager
> catMan
=
290 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
);
292 nsCString contractId
;
293 nsresult rv
= catMan
->GetCategoryEntry("push", mScope
, contractId
);
294 if (NS_SUCCEEDED(rv
)) {
295 // Ensure the service is created - we don't need to do anything with
296 // it though - we assume the service constructor attaches a listener.
297 nsCOMPtr
<nsISupports
> service
= do_GetService(contractId
.get());
300 return obsService
->NotifyObservers(aSubject
, aTopic
,
301 NS_ConvertUTF8toUTF16(mScope
).get());
304 PushMessageDispatcher::PushMessageDispatcher(
305 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
306 const nsAString
& aMessageId
, const Maybe
<nsTArray
<uint8_t>>& aData
)
307 : PushDispatcher(aScope
, aPrincipal
),
308 mMessageId(aMessageId
),
309 mData(aData
? Some(aData
->Clone()) : Nothing()) {}
311 PushMessageDispatcher::~PushMessageDispatcher() = default;
313 nsresult
PushMessageDispatcher::NotifyObservers() {
314 nsCOMPtr
<nsIPushData
> data
;
316 data
= new PushData(mData
.ref());
318 nsCOMPtr
<nsIPushMessage
> message
= new PushMessage(mPrincipal
, data
);
319 return DoNotifyObservers(message
, OBSERVER_TOPIC_PUSH
, mScope
);
322 nsresult
PushMessageDispatcher::NotifyWorkers() {
323 if (!ShouldNotifyWorkers()) {
326 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
328 return NS_ERROR_FAILURE
;
330 nsAutoCString originSuffix
;
331 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
332 if (NS_WARN_IF(NS_FAILED(rv
))) {
335 return swm
->SendPushEvent(originSuffix
, mScope
, mMessageId
, mData
);
338 bool PushMessageDispatcher::SendToParent(ContentChild
* aParentActor
) {
340 return aParentActor
->SendNotifyPushObserversWithData(
341 mScope
, IPC::Principal(mPrincipal
), mMessageId
, mData
.ref());
343 return aParentActor
->SendNotifyPushObservers(
344 mScope
, IPC::Principal(mPrincipal
), mMessageId
);
347 bool PushMessageDispatcher::SendToChild(ContentParent
* aContentActor
) {
349 return aContentActor
->SendPushWithData(mScope
, IPC::Principal(mPrincipal
),
350 mMessageId
, mData
.ref());
352 return aContentActor
->SendPush(mScope
, IPC::Principal(mPrincipal
),
356 PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(
357 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
)
358 : PushDispatcher(aScope
, aPrincipal
) {}
360 PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() = default;
362 nsresult
PushSubscriptionChangeDispatcher::NotifyObservers() {
363 return DoNotifyObservers(mPrincipal
, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE
,
367 nsresult
PushSubscriptionChangeDispatcher::NotifyWorkers() {
368 if (!ShouldNotifyWorkers()) {
371 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
373 return NS_ERROR_FAILURE
;
375 nsAutoCString originSuffix
;
376 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
377 if (NS_WARN_IF(NS_FAILED(rv
))) {
380 return swm
->SendPushSubscriptionChangeEvent(originSuffix
, mScope
);
383 bool PushSubscriptionChangeDispatcher::SendToParent(
384 ContentChild
* aParentActor
) {
385 return aParentActor
->SendNotifyPushSubscriptionChangeObservers(
386 mScope
, IPC::Principal(mPrincipal
));
389 bool PushSubscriptionChangeDispatcher::SendToChild(
390 ContentParent
* aContentActor
) {
391 return aContentActor
->SendPushSubscriptionChange(mScope
,
392 IPC::Principal(mPrincipal
));
395 PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(
396 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
)
397 : PushDispatcher(aScope
, aPrincipal
) {}
399 PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() =
402 nsresult
PushSubscriptionModifiedDispatcher::NotifyObservers() {
403 return DoNotifyObservers(mPrincipal
, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED
,
407 nsresult
PushSubscriptionModifiedDispatcher::NotifyWorkers() { return NS_OK
; }
409 bool PushSubscriptionModifiedDispatcher::SendToParent(
410 ContentChild
* aParentActor
) {
411 return aParentActor
->SendNotifyPushSubscriptionModifiedObservers(
412 mScope
, IPC::Principal(mPrincipal
));
415 bool PushSubscriptionModifiedDispatcher::SendToChild(
416 ContentParent
* aContentActor
) {
417 return aContentActor
->SendNotifyPushSubscriptionModifiedObservers(
418 mScope
, IPC::Principal(mPrincipal
));
421 PushErrorDispatcher::PushErrorDispatcher(const nsACString
& aScope
,
422 nsIPrincipal
* aPrincipal
,
423 const nsAString
& aMessage
,
425 : PushDispatcher(aScope
, aPrincipal
), mMessage(aMessage
), mFlags(aFlags
) {}
427 PushErrorDispatcher::~PushErrorDispatcher() = default;
429 nsresult
PushErrorDispatcher::NotifyObservers() { return NS_OK
; }
431 nsresult
PushErrorDispatcher::NotifyWorkers() {
432 if (!ShouldNotifyWorkers() &&
433 (!mPrincipal
|| mPrincipal
->IsSystemPrincipal())) {
434 // For system subscriptions, log the error directly to the browser console.
435 return nsContentUtils::ReportToConsoleNonLocalized(
436 mMessage
, mFlags
, "Push"_ns
, nullptr, /* aDocument */
440 0, /* aColumnNumber */
441 nsContentUtils::eOMIT_LOCATION
);
444 // For service worker subscriptions, report the error to all clients.
445 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
447 swm
->ReportToAllClients(mScope
, mMessage
,
448 NS_ConvertUTF8toUTF16(mScope
), /* aFilename */
451 0, /* aColumnNumber */
457 bool PushErrorDispatcher::SendToParent(ContentChild
* aContentActor
) {
458 return aContentActor
->SendPushError(mScope
, IPC::Principal(mPrincipal
),
462 bool PushErrorDispatcher::SendToChild(ContentParent
* aContentActor
) {
463 return aContentActor
->SendPushError(mScope
, IPC::Principal(mPrincipal
),
467 nsresult
PushErrorDispatcher::HandleNoChildProcesses() {
468 // Report to the console if no content processes are active.
469 nsCOMPtr
<nsIURI
> scopeURI
;
470 nsresult rv
= NS_NewURI(getter_AddRefs(scopeURI
), mScope
);
471 if (NS_WARN_IF(NS_FAILED(rv
))) {
474 return nsContentUtils::ReportToConsoleNonLocalized(
475 mMessage
, mFlags
, "Push"_ns
, nullptr, /* aDocument */
479 0, /* aColumnNumber */
480 nsContentUtils::eOMIT_LOCATION
);
484 } // namespace mozilla