summaryrefslogtreecommitdiffstats
path: root/dom/push/PushNotifier.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/push/PushNotifier.cpp')
-rw-r--r--dom/push/PushNotifier.cpp550
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