Only global variables are default zero-initialized. In the scenario below, if no messages matches the given `aActionName`, `navigate` is tested for nullity but may be uninitialized, that's UB. Differential Revision: https://phabricator.services.mozilla.com/D323704
192 lines
6.1 KiB
C++
192 lines
6.1 KiB
C++
/* 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 "NotificationHandler.h"
|
|
|
|
#include "NotificationUtils.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/dom/ClientIPCTypes.h"
|
|
#include "mozilla/dom/ClientOpenWindowUtils.h"
|
|
#include "mozilla/dom/DOMTypes.h"
|
|
#include "mozilla/dom/Promise-inl.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/ServiceWorkerManager.h"
|
|
#include "mozilla/dom/ServiceWorkerUtils.h"
|
|
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
|
#include "xpcprivate.h"
|
|
|
|
namespace mozilla::dom::notification {
|
|
|
|
nsresult RespondOnClick(nsIPrincipal* aPrincipal, const nsAString& aScope,
|
|
const IPCNotification& aNotification,
|
|
const nsAString& aActionName) {
|
|
if (StaticPrefs::dom_webnotifications_navigate_enabled()) {
|
|
nsIURI* navigate = nullptr;
|
|
if (aActionName.IsEmpty()) {
|
|
navigate = aNotification.options().navigate();
|
|
} else {
|
|
for (const IPCNotificationAction& action :
|
|
aNotification.options().actions()) {
|
|
if (action.name() == aActionName) {
|
|
navigate = action.navigate();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (navigate) {
|
|
nsAutoCString spec;
|
|
navigate->GetSpec(spec);
|
|
return OpenWindowFor(aPrincipal, spec);
|
|
}
|
|
}
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString originSuffix;
|
|
MOZ_TRY(aPrincipal->GetOriginSuffix(originSuffix));
|
|
|
|
nsresult rv = swm->SendNotificationClickEvent(originSuffix, aScope,
|
|
aNotification, aActionName);
|
|
if (NS_FAILED(rv)) {
|
|
// No active service worker, let's do the last resort
|
|
// TODO(krosylight): We should prevent entering this path as much as
|
|
// possible and ultimately remove this. See bug 1972120.
|
|
return OpenWindowFor(aPrincipal);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult OpenWindowFor(nsIPrincipal* aPrincipal, const nsCString& aURL) {
|
|
nsAutoCString origin;
|
|
MOZ_TRY(aPrincipal->GetOriginNoSuffix(origin));
|
|
if (!aPrincipal->GetIsOriginPotentiallyTrustworthy()) {
|
|
// We expect only secure context origins for web notifications.
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// XXX: We should be able to just pass nsIPrincipal directly
|
|
mozilla::ipc::PrincipalInfo info{};
|
|
MOZ_TRY(PrincipalToPrincipalInfo(aPrincipal, &info));
|
|
|
|
(void)ClientOpenWindow(nullptr,
|
|
ClientOpenWindowArgs(info, Nothing(), aURL, origin));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(NotificationHandler, nsINotificationHandler);
|
|
|
|
StaticRefPtr<NotificationHandler> sHandler;
|
|
|
|
already_AddRefed<NotificationHandler> NotificationHandler::GetSingleton() {
|
|
if (!sHandler) {
|
|
sHandler = new NotificationHandler();
|
|
ClearOnShutdown(&sHandler);
|
|
}
|
|
|
|
return do_AddRef(sHandler);
|
|
}
|
|
|
|
struct NotificationActionComparator {
|
|
bool Equals(const IPCNotificationAction& aAction,
|
|
const nsAString& aActionName) const {
|
|
return aAction.name() == aActionName;
|
|
}
|
|
};
|
|
|
|
NS_IMETHODIMP NotificationHandler::RespondOnClick(
|
|
nsIPrincipal* aPrincipal, const nsAString& aNotificationId,
|
|
const nsAString& aActionName, bool aAutoClosed, Promise** aResult) {
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
// This function is only designed for web notifications.
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsAutoCString origin;
|
|
MOZ_TRY(aPrincipal->GetOrigin(origin));
|
|
|
|
bool isPrivate = aPrincipal->GetIsInPrivateBrowsing();
|
|
nsCOMPtr<nsINotificationStorage> storage = GetNotificationStorage(isPrivate);
|
|
|
|
RefPtr<Promise> promise;
|
|
storage->GetById(origin, aNotificationId, getter_AddRefs(promise));
|
|
|
|
if (aAutoClosed) {
|
|
// The system already closed the notification, let's purge the entry here.
|
|
//
|
|
// It is guaranteed that Delete will happen only immediately after GetById
|
|
// as NotificationDB manages each request with an internal job queue.
|
|
//
|
|
// XXX(krosylight): We should use AUTF8String for all NotificationStorage
|
|
// methods.
|
|
storage->Delete(NS_ConvertUTF8toUTF16(origin), aNotificationId);
|
|
}
|
|
|
|
RefPtr<Promise> result = MOZ_TRY(promise->ThenWithoutCycleCollection(
|
|
[actionName = nsString(aActionName), principal = nsCOMPtr(aPrincipal)](
|
|
JSContext* aCx, JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv) mutable -> already_AddRefed<Promise> {
|
|
auto tryable = [&]() -> nsresult {
|
|
if (aValue.isUndefined()) {
|
|
// No storage entry, open a new window as a fallback
|
|
return OpenWindowFor(principal);
|
|
}
|
|
|
|
MOZ_ASSERT(aValue.isObject());
|
|
JSObject* obj = &aValue.toObject();
|
|
|
|
nsCOMPtr<nsINotificationStorageEntry> entry;
|
|
MOZ_TRY(nsXPConnect::XPConnect()->WrapJS(
|
|
aCx, obj, NS_GET_IID(nsINotificationStorageEntry),
|
|
getter_AddRefs(entry)));
|
|
if (!entry) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoString scope;
|
|
MOZ_TRY(entry->GetServiceWorkerRegistrationScope(scope));
|
|
|
|
nsCOMPtr<nsIURI> scopeURI;
|
|
MOZ_TRY(NS_NewURI(getter_AddRefs(scopeURI), scope));
|
|
|
|
ServiceWorkerScopeIsValid(principal, scopeURI, aRv);
|
|
if (aRv.Failed()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
IPCNotification notification =
|
|
MOZ_TRY(NotificationStorageEntry::ToIPC(*entry));
|
|
|
|
if (!actionName.IsEmpty()) {
|
|
bool contains = notification.options().actions().Contains(
|
|
actionName, NotificationActionComparator());
|
|
if (!contains) {
|
|
// Invalid action, so pretend it had no action
|
|
actionName.Truncate();
|
|
}
|
|
}
|
|
|
|
return notification::RespondOnClick(principal, scope, notification,
|
|
actionName);
|
|
};
|
|
|
|
nsresult rv = tryable();
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}));
|
|
|
|
if (aResult) {
|
|
result.forget(aResult);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::dom::notification
|