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"
25 namespace mozilla::dom
{
27 PushNotifier::PushNotifier() = default;
29 PushNotifier::~PushNotifier() = default;
31 NS_IMPL_CYCLE_COLLECTION_0(PushNotifier
)
33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier
)
34 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushNotifier
)
35 NS_INTERFACE_MAP_ENTRY(nsIPushNotifier
)
38 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier
)
39 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier
)
42 PushNotifier::NotifyPushWithData(const nsACString
& aScope
,
43 nsIPrincipal
* aPrincipal
,
44 const nsAString
& aMessageId
,
45 const nsTArray
<uint8_t>& aData
) {
46 NS_ENSURE_ARG(aPrincipal
);
47 // We still need to do this copying business, if we want the copy to be
48 // fallible. Just passing Some(aData) would do an infallible copy at the
49 // point where the Some() call happens.
50 nsTArray
<uint8_t> data
;
51 if (!data
.AppendElements(aData
, fallible
)) {
52 return NS_ERROR_OUT_OF_MEMORY
;
54 PushMessageDispatcher
dispatcher(aScope
, aPrincipal
, aMessageId
,
55 Some(std::move(data
)));
56 return Dispatch(dispatcher
);
60 PushNotifier::NotifyPush(const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
61 const nsAString
& aMessageId
) {
62 NS_ENSURE_ARG(aPrincipal
);
63 PushMessageDispatcher
dispatcher(aScope
, aPrincipal
, aMessageId
, Nothing());
64 return Dispatch(dispatcher
);
68 PushNotifier::NotifySubscriptionChange(const nsACString
& aScope
,
69 nsIPrincipal
* aPrincipal
) {
70 NS_ENSURE_ARG(aPrincipal
);
71 PushSubscriptionChangeDispatcher
dispatcher(aScope
, aPrincipal
);
72 return Dispatch(dispatcher
);
76 PushNotifier::NotifySubscriptionModified(const nsACString
& aScope
,
77 nsIPrincipal
* aPrincipal
) {
78 NS_ENSURE_ARG(aPrincipal
);
79 PushSubscriptionModifiedDispatcher
dispatcher(aScope
, aPrincipal
);
80 return Dispatch(dispatcher
);
84 PushNotifier::NotifyError(const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
85 const nsAString
& aMessage
, uint32_t aFlags
) {
86 NS_ENSURE_ARG(aPrincipal
);
87 PushErrorDispatcher
dispatcher(aScope
, aPrincipal
, aMessage
, aFlags
);
88 return Dispatch(dispatcher
);
91 nsresult
PushNotifier::Dispatch(PushDispatcher
& aDispatcher
) {
92 if (XRE_IsParentProcess()) {
93 // Always notify XPCOM observers in the parent process.
94 Unused
<< NS_WARN_IF(NS_FAILED(aDispatcher
.NotifyObservers()));
96 // e10s is disabled; notify workers in the parent.
97 return aDispatcher
.NotifyWorkers();
100 // Otherwise, we're in the content process, so e10s must be enabled. Notify
101 // observers and workers, then send a message to notify observers in the
103 MOZ_ASSERT(XRE_IsContentProcess());
105 nsresult rv
= aDispatcher
.NotifyObserversAndWorkers();
107 ContentChild
* parentActor
= ContentChild::GetSingleton();
108 if (!NS_WARN_IF(!parentActor
)) {
109 Unused
<< NS_WARN_IF(!aDispatcher
.SendToParent(parentActor
));
115 PushData::PushData(const nsTArray
<uint8_t>& aData
) : mData(aData
.Clone()) {}
117 PushData::~PushData() = default;
119 NS_IMPL_CYCLE_COLLECTION_0(PushData
)
121 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData
)
122 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushData
)
123 NS_INTERFACE_MAP_ENTRY(nsIPushData
)
126 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData
)
127 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData
)
129 nsresult
PushData::EnsureDecodedText() {
130 if (mData
.IsEmpty() || !mDecodedText
.IsEmpty()) {
133 nsresult rv
= BodyUtil::ConsumeText(
134 mData
.Length(), reinterpret_cast<uint8_t*>(mData
.Elements()),
136 if (NS_WARN_IF(NS_FAILED(rv
))) {
137 mDecodedText
.Truncate();
144 PushData::Text(nsAString
& aText
) {
145 nsresult rv
= EnsureDecodedText();
146 if (NS_WARN_IF(NS_FAILED(rv
))) {
149 aText
= mDecodedText
;
154 PushData::Json(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
) {
155 nsresult rv
= EnsureDecodedText();
156 if (NS_WARN_IF(NS_FAILED(rv
))) {
160 BodyUtil::ConsumeJson(aCx
, aResult
, mDecodedText
, error
);
161 return error
.StealNSResult();
165 PushData::Binary(nsTArray
<uint8_t>& aData
) {
166 aData
= mData
.Clone();
170 PushMessage::PushMessage(nsIPrincipal
* aPrincipal
, nsIPushData
* aData
)
171 : mPrincipal(aPrincipal
), mData(aData
) {}
173 PushMessage::~PushMessage() = default;
175 NS_IMPL_CYCLE_COLLECTION(PushMessage
, mPrincipal
, mData
)
177 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage
)
178 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPushMessage
)
179 NS_INTERFACE_MAP_ENTRY(nsIPushMessage
)
182 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage
)
183 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage
)
186 PushMessage::GetPrincipal(nsIPrincipal
** aPrincipal
) {
187 NS_ENSURE_ARG_POINTER(aPrincipal
);
189 nsCOMPtr
<nsIPrincipal
> principal
= mPrincipal
;
190 principal
.forget(aPrincipal
);
195 PushMessage::GetData(nsIPushData
** aData
) {
196 NS_ENSURE_ARG_POINTER(aData
);
198 nsCOMPtr
<nsIPushData
> data
= mData
;
203 PushDispatcher::PushDispatcher(const nsACString
& aScope
,
204 nsIPrincipal
* aPrincipal
)
205 : mScope(aScope
), mPrincipal(aPrincipal
) {}
207 PushDispatcher::~PushDispatcher() = default;
209 nsresult
PushDispatcher::HandleNoChildProcesses() { return NS_OK
; }
211 nsresult
PushDispatcher::NotifyObserversAndWorkers() {
212 Unused
<< NS_WARN_IF(NS_FAILED(NotifyObservers()));
213 return NotifyWorkers();
216 bool PushDispatcher::ShouldNotifyWorkers() {
217 if (NS_WARN_IF(!mPrincipal
)) {
221 // System subscriptions use observer notifications instead of service worker
222 // events. The `testing.notifyWorkers` pref disables worker events for
223 // non-system subscriptions.
224 if (mPrincipal
->IsSystemPrincipal() ||
225 !Preferences::GetBool("dom.push.testing.notifyWorkers", true)) {
229 // If e10s is off, no need to worry about processes.
230 if (!BrowserTabsRemoteAutostart()) {
234 // We only want to notify in the parent process.
235 bool isContentProcess
= XRE_GetProcessType() == GeckoProcessType_Content
;
236 return !isContentProcess
;
239 nsresult
PushDispatcher::DoNotifyObservers(nsISupports
* aSubject
,
241 const nsACString
& aScope
) {
242 nsCOMPtr
<nsIObserverService
> obsService
=
243 mozilla::services::GetObserverService();
245 return NS_ERROR_FAILURE
;
247 // If there's a service for this push category, make sure it is alive.
248 nsCOMPtr
<nsICategoryManager
> catMan
=
249 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
);
251 nsCString contractId
;
252 nsresult rv
= catMan
->GetCategoryEntry("push", mScope
, contractId
);
253 if (NS_SUCCEEDED(rv
)) {
254 // Ensure the service is created - we don't need to do anything with
255 // it though - we assume the service constructor attaches a listener.
256 nsCOMPtr
<nsISupports
> service
= do_GetService(contractId
.get());
259 return obsService
->NotifyObservers(aSubject
, aTopic
,
260 NS_ConvertUTF8toUTF16(mScope
).get());
263 PushMessageDispatcher::PushMessageDispatcher(
264 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
,
265 const nsAString
& aMessageId
, const Maybe
<nsTArray
<uint8_t>>& aData
)
266 : PushDispatcher(aScope
, aPrincipal
),
267 mMessageId(aMessageId
),
268 mData(aData
? Some(aData
->Clone()) : Nothing()) {}
270 PushMessageDispatcher::~PushMessageDispatcher() = default;
272 nsresult
PushMessageDispatcher::NotifyObservers() {
273 nsCOMPtr
<nsIPushData
> data
;
275 data
= new PushData(mData
.ref());
277 nsCOMPtr
<nsIPushMessage
> message
= new PushMessage(mPrincipal
, data
);
278 return DoNotifyObservers(message
, OBSERVER_TOPIC_PUSH
, mScope
);
281 nsresult
PushMessageDispatcher::NotifyWorkers() {
282 if (!ShouldNotifyWorkers()) {
285 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
287 return NS_ERROR_FAILURE
;
289 nsAutoCString originSuffix
;
290 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
291 if (NS_WARN_IF(NS_FAILED(rv
))) {
294 return swm
->SendPushEvent(originSuffix
, mScope
, mMessageId
, mData
);
297 bool PushMessageDispatcher::SendToParent(ContentChild
* aParentActor
) {
299 return aParentActor
->SendNotifyPushObserversWithData(
300 mScope
, mPrincipal
, mMessageId
, mData
.ref());
302 return aParentActor
->SendNotifyPushObservers(mScope
, mPrincipal
, mMessageId
);
305 bool PushMessageDispatcher::SendToChild(ContentParent
* aContentActor
) {
307 return aContentActor
->SendPushWithData(mScope
, mPrincipal
, mMessageId
,
310 return aContentActor
->SendPush(mScope
, mPrincipal
, mMessageId
);
313 PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(
314 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
)
315 : PushDispatcher(aScope
, aPrincipal
) {}
317 PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() = default;
319 nsresult
PushSubscriptionChangeDispatcher::NotifyObservers() {
320 return DoNotifyObservers(mPrincipal
, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE
,
324 nsresult
PushSubscriptionChangeDispatcher::NotifyWorkers() {
325 if (!ShouldNotifyWorkers()) {
328 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
330 return NS_ERROR_FAILURE
;
332 nsAutoCString originSuffix
;
333 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
334 if (NS_WARN_IF(NS_FAILED(rv
))) {
337 return swm
->SendPushSubscriptionChangeEvent(originSuffix
, mScope
);
340 bool PushSubscriptionChangeDispatcher::SendToParent(
341 ContentChild
* aParentActor
) {
342 return aParentActor
->SendNotifyPushSubscriptionChangeObservers(mScope
,
346 bool PushSubscriptionChangeDispatcher::SendToChild(
347 ContentParent
* aContentActor
) {
348 return aContentActor
->SendPushSubscriptionChange(mScope
, mPrincipal
);
351 PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(
352 const nsACString
& aScope
, nsIPrincipal
* aPrincipal
)
353 : PushDispatcher(aScope
, aPrincipal
) {}
355 PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() =
358 nsresult
PushSubscriptionModifiedDispatcher::NotifyObservers() {
359 return DoNotifyObservers(mPrincipal
, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED
,
363 nsresult
PushSubscriptionModifiedDispatcher::NotifyWorkers() { return NS_OK
; }
365 bool PushSubscriptionModifiedDispatcher::SendToParent(
366 ContentChild
* aParentActor
) {
367 return aParentActor
->SendNotifyPushSubscriptionModifiedObservers(mScope
,
371 bool PushSubscriptionModifiedDispatcher::SendToChild(
372 ContentParent
* aContentActor
) {
373 return aContentActor
->SendNotifyPushSubscriptionModifiedObservers(mScope
,
377 PushErrorDispatcher::PushErrorDispatcher(const nsACString
& aScope
,
378 nsIPrincipal
* aPrincipal
,
379 const nsAString
& aMessage
,
381 : PushDispatcher(aScope
, aPrincipal
), mMessage(aMessage
), mFlags(aFlags
) {}
383 PushErrorDispatcher::~PushErrorDispatcher() = default;
385 nsresult
PushErrorDispatcher::NotifyObservers() { return NS_OK
; }
387 nsresult
PushErrorDispatcher::NotifyWorkers() {
388 if (!ShouldNotifyWorkers() &&
389 (!mPrincipal
|| mPrincipal
->IsSystemPrincipal())) {
390 // For system subscriptions, log the error directly to the browser console.
391 return nsContentUtils::ReportToConsoleNonLocalized(
392 mMessage
, mFlags
, "Push"_ns
, nullptr, /* aDocument */
396 // For service worker subscriptions, report the error to all clients.
397 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
399 swm
->ReportToAllClients(mScope
, mMessage
, mScope
, /* aFilename */
402 0, /* aColumnNumber */
408 bool PushErrorDispatcher::SendToParent(ContentChild
* aContentActor
) {
409 return aContentActor
->SendPushError(mScope
, mPrincipal
, mMessage
, mFlags
);
412 bool PushErrorDispatcher::SendToChild(ContentParent
* aContentActor
) {
413 return aContentActor
->SendPushError(mScope
, mPrincipal
, mMessage
, mFlags
);
416 nsresult
PushErrorDispatcher::HandleNoChildProcesses() {
417 // Report to the console if no content processes are active.
418 nsCOMPtr
<nsIURI
> scopeURI
;
419 nsresult rv
= NS_NewURI(getter_AddRefs(scopeURI
), mScope
);
420 if (NS_WARN_IF(NS_FAILED(rv
))) {
423 return nsContentUtils::ReportToConsoleNonLocalized(
424 mMessage
, mFlags
, "Push"_ns
, /* aDocument = */ nullptr,
425 SourceLocation(scopeURI
.get()));
428 } // namespace mozilla::dom