mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 08:15:31 +00:00
3eed3c5f94
Summary: The push notifier needs to proxy its calls to the proper process in order to notify service workers. With parent-intercept enabled, that means making sure we notify in the parent and without it, that we notify in the content process. Fortunately, we already have to do this proxying for the observer notifications, so we can just piggyback on top of that code to make things work for service workers. Reviewers: asuth Reviewed By: asuth Bug #: 1517406 Differential Revision: https://phabricator.services.mozilla.com/D15634 --HG-- extra : rebase_source : a29216f3a6638592a784e7e830fe9f7842cce7e9
493 lines
16 KiB
C++
493 lines
16 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "PushNotifier.h"
|
|
|
|
#include "nsContentUtils.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsICategoryManager.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsXPCOM.h"
|
|
#include "mozilla/dom/ServiceWorkerManager.h"
|
|
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include "mozilla/dom/BodyUtil.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
PushNotifier::PushNotifier() {}
|
|
|
|
PushNotifier::~PushNotifier() {}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_0(PushNotifier)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushNotifier)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPushNotifier)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
|
|
|
|
NS_IMETHODIMP
|
|
PushNotifier::NotifyPushWithData(const nsACString& aScope,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsAString& aMessageId, uint32_t aDataLen,
|
|
uint8_t* aData) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
nsTArray<uint8_t> data;
|
|
if (!data.SetCapacity(aDataLen, fallible)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(data));
|
|
return Dispatch(dispatcher);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
|
|
const nsAString& aMessageId) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
|
|
return Dispatch(dispatcher);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
|
|
nsIPrincipal* aPrincipal) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal);
|
|
return Dispatch(dispatcher);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
|
|
nsIPrincipal* aPrincipal) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
|
|
return Dispatch(dispatcher);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
|
|
const nsAString& aMessage, uint32_t aFlags) {
|
|
NS_ENSURE_ARG(aPrincipal);
|
|
PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags);
|
|
return Dispatch(dispatcher);
|
|
}
|
|
|
|
nsresult PushNotifier::Dispatch(PushDispatcher& aDispatcher) {
|
|
if (XRE_IsParentProcess()) {
|
|
// Always notify XPCOM observers in the parent process.
|
|
Unused << NS_WARN_IF(NS_FAILED(aDispatcher.NotifyObservers()));
|
|
|
|
nsTArray<ContentParent*> contentActors;
|
|
ContentParent::GetAll(contentActors);
|
|
if (!contentActors.IsEmpty() && !ServiceWorkerParentInterceptEnabled()) {
|
|
// At least one content process is active, so e10s must be enabled.
|
|
// Broadcast a message to notify observers and service workers.
|
|
for (uint32_t i = 0; i < contentActors.Length(); ++i) {
|
|
// We need to filter based on process type, only "web" AKA the default
|
|
// remote type is acceptable.
|
|
if (!contentActors[i]->GetRemoteType().EqualsLiteral(
|
|
DEFAULT_REMOTE_TYPE)) {
|
|
continue;
|
|
}
|
|
|
|
// Ensure that the content actor has the permissions avaliable for the
|
|
// principal the push is being sent for before sending the push message
|
|
// down.
|
|
Unused << contentActors[i]->TransmitPermissionsForPrincipal(
|
|
aDispatcher.GetPrincipal());
|
|
if (aDispatcher.SendToChild(contentActors[i])) {
|
|
// Only send the push message to the first content process to avoid
|
|
// multiple SWs showing the same notification. See bug 1300112.
|
|
break;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (BrowserTabsRemoteAutostart() &&
|
|
!ServiceWorkerParentInterceptEnabled()) {
|
|
// e10s is enabled, but no content processes are active.
|
|
return aDispatcher.HandleNoChildProcesses();
|
|
}
|
|
|
|
// e10s is disabled; notify workers in the parent.
|
|
return aDispatcher.NotifyWorkers();
|
|
}
|
|
|
|
// Otherwise, we're in the content process, so e10s must be enabled. Notify
|
|
// observers and workers, then send a message to notify observers in the
|
|
// parent.
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
|
|
nsresult rv = aDispatcher.NotifyObserversAndWorkers();
|
|
|
|
ContentChild* parentActor = ContentChild::GetSingleton();
|
|
if (!NS_WARN_IF(!parentActor)) {
|
|
Unused << NS_WARN_IF(!aDispatcher.SendToParent(parentActor));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
PushData::PushData(const nsTArray<uint8_t>& aData) : mData(aData) {}
|
|
|
|
PushData::~PushData() {}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_0(PushData)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPushData)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData)
|
|
|
|
nsresult PushData::EnsureDecodedText() {
|
|
if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
nsresult rv = BodyUtil::ConsumeText(
|
|
mData.Length(), reinterpret_cast<uint8_t*>(mData.Elements()),
|
|
mDecodedText);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mDecodedText.Truncate();
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushData::Text(nsAString& aText) {
|
|
nsresult rv = EnsureDecodedText();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
aText = mDecodedText;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushData::Json(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
|
|
nsresult rv = EnsureDecodedText();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
ErrorResult error;
|
|
BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushData::Binary(uint32_t* aDataLen, uint8_t** aData) {
|
|
NS_ENSURE_ARG_POINTER(aDataLen);
|
|
NS_ENSURE_ARG_POINTER(aData);
|
|
|
|
*aData = nullptr;
|
|
if (mData.IsEmpty()) {
|
|
*aDataLen = 0;
|
|
return NS_OK;
|
|
}
|
|
uint32_t length = mData.Length();
|
|
uint8_t* data = static_cast<uint8_t*>(moz_xmalloc(length * sizeof(uint8_t)));
|
|
memcpy(data, mData.Elements(), length * sizeof(uint8_t));
|
|
*aDataLen = length;
|
|
*aData = data;
|
|
return NS_OK;
|
|
}
|
|
|
|
PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
|
|
: mPrincipal(aPrincipal), mData(aData) {}
|
|
|
|
PushMessage::~PushMessage() {}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
|
|
|
|
NS_IMETHODIMP
|
|
PushMessage::GetPrincipal(nsIPrincipal** aPrincipal) {
|
|
NS_ENSURE_ARG_POINTER(aPrincipal);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = mPrincipal;
|
|
principal.forget(aPrincipal);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PushMessage::GetData(nsIPushData** aData) {
|
|
NS_ENSURE_ARG_POINTER(aData);
|
|
|
|
nsCOMPtr<nsIPushData> data = mData;
|
|
data.forget(aData);
|
|
return NS_OK;
|
|
}
|
|
|
|
PushDispatcher::PushDispatcher(const nsACString& aScope,
|
|
nsIPrincipal* aPrincipal)
|
|
: mScope(aScope), mPrincipal(aPrincipal) {}
|
|
|
|
PushDispatcher::~PushDispatcher() {}
|
|
|
|
nsresult PushDispatcher::HandleNoChildProcesses() { return NS_OK; }
|
|
|
|
nsresult PushDispatcher::NotifyObserversAndWorkers() {
|
|
Unused << NS_WARN_IF(NS_FAILED(NotifyObservers()));
|
|
return NotifyWorkers();
|
|
}
|
|
|
|
bool PushDispatcher::ShouldNotifyWorkers() {
|
|
if (NS_WARN_IF(!mPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
// System subscriptions use observer notifications instead of service worker
|
|
// events. The `testing.notifyWorkers` pref disables worker events for
|
|
// non-system subscriptions.
|
|
if (nsContentUtils::IsSystemPrincipal(mPrincipal) ||
|
|
!Preferences::GetBool("dom.push.testing.notifyWorkers", true)) {
|
|
return false;
|
|
}
|
|
|
|
// If e10s is off, no need to worry about processes.
|
|
if (!BrowserTabsRemoteAutostart()) {
|
|
return true;
|
|
}
|
|
|
|
// If parent intercept is enabled, then we only want to notify in the parent
|
|
// process. Otherwise, we only want to notify in the child process.
|
|
bool isContentProcess = XRE_GetProcessType() == GeckoProcessType_Content;
|
|
bool parentInterceptEnabled = ServiceWorkerParentInterceptEnabled();
|
|
if (parentInterceptEnabled) {
|
|
return !isContentProcess;
|
|
}
|
|
|
|
return isContentProcess;
|
|
}
|
|
|
|
nsresult PushDispatcher::DoNotifyObservers(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const nsACString& aScope) {
|
|
nsCOMPtr<nsIObserverService> obsService =
|
|
mozilla::services::GetObserverService();
|
|
if (!obsService) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// If there's a service for this push category, make sure it is alive.
|
|
nsCOMPtr<nsICategoryManager> catMan =
|
|
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
|
|
if (catMan) {
|
|
nsCString contractId;
|
|
nsresult rv = catMan->GetCategoryEntry("push", mScope, contractId);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// Ensure the service is created - we don't need to do anything with
|
|
// it though - we assume the service constructor attaches a listener.
|
|
nsCOMPtr<nsISupports> service = do_GetService(contractId.get());
|
|
}
|
|
}
|
|
return obsService->NotifyObservers(aSubject, aTopic,
|
|
NS_ConvertUTF8toUTF16(mScope).get());
|
|
}
|
|
|
|
PushMessageDispatcher::PushMessageDispatcher(
|
|
const nsACString& aScope, nsIPrincipal* aPrincipal,
|
|
const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData)
|
|
: PushDispatcher(aScope, aPrincipal),
|
|
mMessageId(aMessageId),
|
|
mData(aData) {}
|
|
|
|
PushMessageDispatcher::~PushMessageDispatcher() {}
|
|
|
|
nsresult PushMessageDispatcher::NotifyObservers() {
|
|
nsCOMPtr<nsIPushData> data;
|
|
if (mData) {
|
|
data = new PushData(mData.ref());
|
|
}
|
|
nsCOMPtr<nsIPushMessage> message = new PushMessage(mPrincipal, data);
|
|
return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, mScope);
|
|
}
|
|
|
|
nsresult PushMessageDispatcher::NotifyWorkers() {
|
|
if (!ShouldNotifyWorkers()) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
nsAutoCString originSuffix;
|
|
nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return swm->SendPushEvent(originSuffix, mScope, mMessageId, mData);
|
|
}
|
|
|
|
bool PushMessageDispatcher::SendToParent(ContentChild* aParentActor) {
|
|
if (mData) {
|
|
return aParentActor->SendNotifyPushObserversWithData(
|
|
mScope, IPC::Principal(mPrincipal), mMessageId, mData.ref());
|
|
}
|
|
return aParentActor->SendNotifyPushObservers(
|
|
mScope, IPC::Principal(mPrincipal), mMessageId);
|
|
}
|
|
|
|
bool PushMessageDispatcher::SendToChild(ContentParent* aContentActor) {
|
|
if (mData) {
|
|
return aContentActor->SendPushWithData(mScope, IPC::Principal(mPrincipal),
|
|
mMessageId, mData.ref());
|
|
}
|
|
return aContentActor->SendPush(mScope, IPC::Principal(mPrincipal),
|
|
mMessageId);
|
|
}
|
|
|
|
PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(
|
|
const nsACString& aScope, nsIPrincipal* aPrincipal)
|
|
: PushDispatcher(aScope, aPrincipal) {}
|
|
|
|
PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() {}
|
|
|
|
nsresult PushSubscriptionChangeDispatcher::NotifyObservers() {
|
|
return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
|
|
mScope);
|
|
}
|
|
|
|
nsresult PushSubscriptionChangeDispatcher::NotifyWorkers() {
|
|
if (!ShouldNotifyWorkers()) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
nsAutoCString originSuffix;
|
|
nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return swm->SendPushSubscriptionChangeEvent(originSuffix, mScope);
|
|
}
|
|
|
|
bool PushSubscriptionChangeDispatcher::SendToParent(
|
|
ContentChild* aParentActor) {
|
|
return aParentActor->SendNotifyPushSubscriptionChangeObservers(
|
|
mScope, IPC::Principal(mPrincipal));
|
|
}
|
|
|
|
bool PushSubscriptionChangeDispatcher::SendToChild(
|
|
ContentParent* aContentActor) {
|
|
return aContentActor->SendPushSubscriptionChange(mScope,
|
|
IPC::Principal(mPrincipal));
|
|
}
|
|
|
|
PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(
|
|
const nsACString& aScope, nsIPrincipal* aPrincipal)
|
|
: PushDispatcher(aScope, aPrincipal) {}
|
|
|
|
PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() {}
|
|
|
|
nsresult PushSubscriptionModifiedDispatcher::NotifyObservers() {
|
|
return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
|
|
mScope);
|
|
}
|
|
|
|
nsresult PushSubscriptionModifiedDispatcher::NotifyWorkers() { return NS_OK; }
|
|
|
|
bool PushSubscriptionModifiedDispatcher::SendToParent(
|
|
ContentChild* aParentActor) {
|
|
return aParentActor->SendNotifyPushSubscriptionModifiedObservers(
|
|
mScope, IPC::Principal(mPrincipal));
|
|
}
|
|
|
|
bool PushSubscriptionModifiedDispatcher::SendToChild(
|
|
ContentParent* aContentActor) {
|
|
return aContentActor->SendNotifyPushSubscriptionModifiedObservers(
|
|
mScope, IPC::Principal(mPrincipal));
|
|
}
|
|
|
|
PushErrorDispatcher::PushErrorDispatcher(const nsACString& aScope,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsAString& aMessage,
|
|
uint32_t aFlags)
|
|
: PushDispatcher(aScope, aPrincipal), mMessage(aMessage), mFlags(aFlags) {}
|
|
|
|
PushErrorDispatcher::~PushErrorDispatcher() {}
|
|
|
|
nsresult PushErrorDispatcher::NotifyObservers() { return NS_OK; }
|
|
|
|
nsresult PushErrorDispatcher::NotifyWorkers() {
|
|
if (!ShouldNotifyWorkers() &&
|
|
(!mPrincipal || nsContentUtils::IsSystemPrincipal(mPrincipal))) {
|
|
// For system subscriptions, log the error directly to the browser console.
|
|
return nsContentUtils::ReportToConsoleNonLocalized(
|
|
mMessage, mFlags, NS_LITERAL_CSTRING("Push"), nullptr, /* aDocument */
|
|
nullptr, /* aURI */
|
|
EmptyString(), /* aLine */
|
|
0, /* aLineNumber */
|
|
0, /* aColumnNumber */
|
|
nsContentUtils::eOMIT_LOCATION);
|
|
}
|
|
|
|
// For service worker subscriptions, report the error to all clients.
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (swm) {
|
|
swm->ReportToAllClients(mScope, mMessage,
|
|
NS_ConvertUTF8toUTF16(mScope), /* aFilename */
|
|
EmptyString(), /* aLine */
|
|
0, /* aLineNumber */
|
|
0, /* aColumnNumber */
|
|
mFlags);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool PushErrorDispatcher::SendToParent(ContentChild* aContentActor) {
|
|
return aContentActor->SendPushError(mScope, IPC::Principal(mPrincipal),
|
|
mMessage, mFlags);
|
|
}
|
|
|
|
bool PushErrorDispatcher::SendToChild(ContentParent* aContentActor) {
|
|
return aContentActor->SendPushError(mScope, IPC::Principal(mPrincipal),
|
|
mMessage, mFlags);
|
|
}
|
|
|
|
nsresult PushErrorDispatcher::HandleNoChildProcesses() {
|
|
// Report to the console if no content processes are active.
|
|
nsCOMPtr<nsIURI> scopeURI;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return nsContentUtils::ReportToConsoleNonLocalized(
|
|
mMessage, mFlags, NS_LITERAL_CSTRING("Push"), nullptr, /* aDocument */
|
|
scopeURI, /* aURI */
|
|
EmptyString(), /* aLine */
|
|
0, /* aLineNumber */
|
|
0, /* aColumnNumber */
|
|
nsContentUtils::eOMIT_LOCATION);
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|