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.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/dom/push/PushNotifier.cpp b/dom/push/PushNotifier.cpp
new file mode 100644
index 0000000000..d74e101cb1
--- /dev/null
+++ b/dom/push/PushNotifier.cpp
@@ -0,0 +1,484 @@
+/* -*- 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/BasePrincipal.h"
+#include "mozilla/Preferences.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() = default;
+
+PushNotifier::~PushNotifier() = default;
+
+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,
+ const nsTArray<uint8_t>& aData) {
+ NS_ENSURE_ARG(aPrincipal);
+ // We still need to do this copying business, if we want the copy to be
+ // fallible. Just passing Some(aData) would do an infallible copy at the
+ // point where the Some() call happens.
+ nsTArray<uint8_t> data;
+ if (!data.AppendElements(aData, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId,
+ Some(std::move(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. This should not run when Fission is
+ // enabled, and we specifically don't want this for
+ // LARGE_ALLOCATION_REMOTE_TYPE, so don't use IsWebRemoteType().
+ if (contentActors[i]->GetRemoteType() != 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.Clone()) {}
+
+PushData::~PushData() = default;
+
+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(nsTArray<uint8_t>& aData) {
+ aData = mData.Clone();
+ return NS_OK;
+}
+
+PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
+ : mPrincipal(aPrincipal), mData(aData) {}
+
+PushMessage::~PushMessage() = default;
+
+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() = default;
+
+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 (mPrincipal->IsSystemPrincipal() ||
+ !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 ? Some(aData->Clone()) : Nothing()) {}
+
+PushMessageDispatcher::~PushMessageDispatcher() = default;
+
+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() = default;
+
+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() =
+ default;
+
+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() = default;
+
+nsresult PushErrorDispatcher::NotifyObservers() { return NS_OK; }
+
+nsresult PushErrorDispatcher::NotifyWorkers() {
+ if (!ShouldNotifyWorkers() &&
+ (!mPrincipal || mPrincipal->IsSystemPrincipal())) {
+ // For system subscriptions, log the error directly to the browser console.
+ return nsContentUtils::ReportToConsoleNonLocalized(
+ mMessage, mFlags, "Push"_ns, nullptr, /* aDocument */
+ nullptr, /* aURI */
+ u""_ns, /* 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 */
+ u""_ns, /* 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, "Push"_ns, nullptr, /* aDocument */
+ scopeURI, /* aURI */
+ u""_ns, /* aLine */
+ 0, /* aLineNumber */
+ 0, /* aColumnNumber */
+ nsContentUtils::eOMIT_LOCATION);
+}
+
+} // namespace dom
+} // namespace mozilla