diff options
Diffstat (limited to 'dom/push/PushNotifier.cpp')
-rw-r--r-- | dom/push/PushNotifier.cpp | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/dom/push/PushNotifier.cpp b/dom/push/PushNotifier.cpp new file mode 100644 index 000000000..e60db2d97 --- /dev/null +++ b/dom/push/PushNotifier.cpp @@ -0,0 +1,550 @@ +/* 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 "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 { + +using workers::AssertIsOnMainThread; +using workers::ServiceWorkerManager; + +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()) { + // 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) { + Unused << NS_WARN_IF(!aDispatcher.SendToChild(contentActors[i])); + } + return NS_OK; + } + + if (BrowserTabsRemoteAutostart()) { + // 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*>(NS_Alloc(length * sizeof(uint8_t))); + if (!data) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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. + return !nsContentUtils::IsSystemPrincipal(mPrincipal) && + Preferences::GetBool("dom.push.testing.notifyWorkers", true); +} + +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) { + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("push", + mScope.BeginReading(), + getter_Copies(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); + } + } + 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()) { + // 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*) +{ + return true; +} + +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 |