From 517e37a9da99071a81df34fdbe8d57cbcd7fb91c Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Mon, 3 Mar 2025 16:48:49 +0000 Subject: [PATCH] Bug 1950117 - Add PServiceWorkerRegistration::GetNotifications r=asuth Differential Revision: https://phabricator.services.mozilla.com/D239793 --- dom/ipc/DOMTypes.ipdlh | 5 + dom/notification/Notification.cpp | 340 ------------------ dom/notification/Notification.h | 25 -- dom/notification/moz.build | 2 +- .../PServiceWorkerRegistration.ipdl | 2 + .../ServiceWorkerRegistration.cpp | 81 ++++- .../ServiceWorkerRegistrationParent.cpp | 17 + .../ServiceWorkerRegistrationParent.h | 3 + .../ServiceWorkerRegistrationProxy.cpp | 85 +++++ .../ServiceWorkerRegistrationProxy.h | 2 + dom/serviceworkers/ServiceWorkerUtils.h | 4 + .../registration-association.https.window.js | 11 +- 12 files changed, 197 insertions(+), 380 deletions(-) diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh index 2a7b6b228268..42f34f16b378 100644 --- a/dom/ipc/DOMTypes.ipdlh +++ b/dom/ipc/DOMTypes.ipdlh @@ -359,5 +359,10 @@ struct IPCNotification { IPCNotificationOptions options; }; +union IPCNotificationsOrError { + IPCNotification[]; + nsresult; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 88ba0dc85395..f5926e1f13f8 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -70,122 +70,6 @@ struct NotificationStrings { const nsString mServiceWorkerRegistrationScope; }; -class GetCallbackBase : public nsINotificationStorageCallback { - public: - NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle, - const nsAString& aDir, const nsAString& aLang, - const nsAString& aBody, const nsAString& aTag, - const nsAString& aIcon, const nsAString& aData, - const nsAString& aServiceWorkerRegistrationScope) final { - AssertIsOnMainThread(); - MOZ_ASSERT(!aID.IsEmpty()); - - NotificationStrings strings = { - nsString(aID), - nsString(aTitle), - nsString(aDir), - nsString(aLang), - nsString(aBody), - nsString(aTag), - nsString(aIcon), - nsString(aData), - nsString(aServiceWorkerRegistrationScope), - }; - - mStrings.AppendElement(std::move(strings)); - return NS_OK; - } - - NS_IMETHOD Done() override = 0; - - protected: - virtual ~GetCallbackBase() = default; - - nsTArray mStrings; -}; - -class NotificationStorageCallback final : public GetCallbackBase { - public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback) - - NotificationStorageCallback(nsIGlobalObject* aWindow, Promise* aPromise) - : mWindow(aWindow), mPromise(aPromise) { - AssertIsOnMainThread(); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aPromise); - } - - NS_IMETHOD Done() final { - nsTArray> notifications(mStrings.Length()); - - for (const NotificationStrings& strings : mStrings) { - auto result = Notification::ConstructFromFields( - mWindow, strings.mID, strings.mTitle, strings.mDir, strings.mLang, - strings.mBody, strings.mTag, strings.mIcon, strings.mData, - strings.mServiceWorkerRegistrationScope); - if (result.isErr()) { - continue; - } - RefPtr n = result.unwrap(); - notifications.AppendElement(n.forget()); - } - - mPromise->MaybeResolve(notifications); - return NS_OK; - } - - private: - virtual ~NotificationStorageCallback() = default; - - nsCOMPtr mWindow; - RefPtr mPromise; - const nsString mScope; -}; - -NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback) -NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback) -NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise); - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback) - NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -class NotificationGetRunnable final : public Runnable { - bool mIsPrivate; - const nsString mOrigin; - const nsString mScope; - const nsString mTag; - nsCOMPtr mCallback; - - public: - NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aScope, - const nsAString& aTag, - nsINotificationStorageCallback* aCallback, - bool aIsPrivate) - : Runnable("NotificationGetRunnable"), - mIsPrivate(aIsPrivate), - mOrigin(aOrigin), - mScope(aScope), - mTag(aTag), - mCallback(aCallback) {} - - NS_IMETHOD - Run() override { - nsCOMPtr notificationStorage = - GetNotificationStorage(mIsPrivate); - if (NS_WARN_IF(!notificationStorage)) { - return NS_ERROR_UNEXPECTED; - } - - nsresult rv = notificationStorage->Get(mOrigin, mScope, mTag, mCallback); - // XXXnsm Is it guaranteed mCallback will be called in case of failure? - Unused << NS_WARN_IF(NS_FAILED(rv)); - return rv; - } -}; - class NotificationPermissionRequest : public ContentPermissionRequestBase, public nsIRunnable, public nsINamed { @@ -473,36 +357,6 @@ already_AddRefed Notification::Constructor( return notification.forget(); } -// static -Result, QMResult> -Notification::ConstructFromFields( - nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle, - const nsAString& aDir, const nsAString& aLang, const nsAString& aBody, - const nsAString& aTag, const nsAString& aIcon, const nsAString& aData, - const nsAString& aServiceWorkerRegistrationScope) { - MOZ_ASSERT(aGlobal); - - RootedDictionary options(RootingCx()); - options.mDir = StringToEnum(aDir).valueOr( - NotificationDirection::Auto); - options.mLang = aLang; - options.mBody = aBody; - options.mTag = aTag; - options.mIcon = aIcon; - IgnoredErrorResult rv; - RefPtr notification = - CreateInternal(aGlobal, aID, aTitle, options, rv); - if (NS_WARN_IF(rv.Failed())) { - return Err(ToQMResult(NS_ERROR_FAILURE)); - } - - QM_TRY(notification->InitFromBase64(aData)); - - notification->SetScope(aServiceWorkerRegistrationScope); - - return notification.forget(); -} - // static Result, QMResult> Notification::ConstructFromIPC( nsIGlobalObject* aGlobal, const IPCNotification& aIPCNotification, @@ -797,200 +651,6 @@ nsresult Notification::ResolveIconURL(nsIGlobalObject* aGlobal, return rv; } -already_AddRefed Notification::Get( - nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter, - const nsAString& aScope, ErrorResult& aRv) { - AssertIsOnMainThread(); - MOZ_ASSERT(aWindow); - - nsCOMPtr doc = aWindow->GetExtantDoc(); - if (!doc) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } - - nsString origin; - aRv = GetOrigin(doc->NodePrincipal(), origin); - if (aRv.Failed()) { - return nullptr; - } - - RefPtr promise = Promise::Create(aWindow->AsGlobal(), aRv); - if (aRv.Failed()) { - return nullptr; - } - - nsCOMPtr callback = - new NotificationStorageCallback(aWindow->AsGlobal(), promise); - - RefPtr r = new NotificationGetRunnable( - origin, aScope, aFilter.mTag, callback, doc->IsInPrivateBrowsing()); - - aRv = aWindow->AsGlobal()->Dispatch(r.forget()); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - return promise.forget(); -} - -class WorkerGetResultRunnable final : public NotificationWorkerRunnable { - RefPtr mPromiseProxy; - const nsTArray mStrings; - - public: - WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate, - PromiseWorkerProxy* aPromiseProxy, - nsTArray&& aStrings) - : NotificationWorkerRunnable(aWorkerPrivate, "WorkerGetResultRunnable"), - mPromiseProxy(aPromiseProxy), - mStrings(std::move(aStrings)) {} - - void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override { - RefPtr workerPromise = mPromiseProxy->GetWorkerPromise(); - // Once Worker had already started shutdown, workerPromise would be nullptr - if (!workerPromise) { - return; - } - - nsTArray> notifications(mStrings.Length()); - for (const NotificationStrings& strings : mStrings) { - auto result = Notification::ConstructFromFields( - aWorkerPrivate->GlobalScope(), strings.mID, strings.mTitle, - strings.mDir, strings.mLang, strings.mBody, strings.mTag, - strings.mIcon, strings.mData, - /* strings.mBehavior, not - * supported */ - strings.mServiceWorkerRegistrationScope); - if (result.isErr()) { - continue; - } - RefPtr n = result.unwrap(); - notifications.AppendElement(n.forget()); - } - - workerPromise->MaybeResolve(notifications); - mPromiseProxy->CleanUp(); - } -}; - -class WorkerGetCallback final : public GetCallbackBase { - RefPtr mPromiseProxy; - - public: - NS_DECL_ISUPPORTS - - explicit WorkerGetCallback(PromiseWorkerProxy* aProxy) - : mPromiseProxy(aProxy) { - AssertIsOnMainThread(); - MOZ_ASSERT(aProxy); - } - - NS_IMETHOD Done() final { - AssertIsOnMainThread(); - MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?"); - - RefPtr proxy = std::move(mPromiseProxy); - MutexAutoLock lock(proxy->Lock()); - if (proxy->CleanedUp()) { - return NS_OK; - } - - RefPtr r = new WorkerGetResultRunnable( - proxy->GetWorkerPrivate(), proxy, std::move(mStrings)); - - r->Dispatch(proxy->GetWorkerPrivate()); - return NS_OK; - } - - private: - ~WorkerGetCallback() = default; -}; - -NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback) - -class WorkerGetRunnable final : public Runnable { - RefPtr mPromiseProxy; - const nsString mTag; - const nsString mScope; - - public: - WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag, - const nsAString& aScope) - : Runnable("WorkerGetRunnable"), - mPromiseProxy(aProxy), - mTag(aTag), - mScope(aScope) { - MOZ_ASSERT(mPromiseProxy); - } - - NS_IMETHOD - Run() override { - AssertIsOnMainThread(); - - MutexAutoLock lock(mPromiseProxy->Lock()); - if (mPromiseProxy->CleanedUp()) { - return NS_OK; - } - - auto* principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal(); - auto isPrivate = principal->GetIsInPrivateBrowsing(); - - nsCOMPtr callback = - new WorkerGetCallback(mPromiseProxy); - - nsCOMPtr notificationStorage = - GetNotificationStorage(isPrivate); - if (NS_WARN_IF(!notificationStorage)) { - callback->Done(); - return NS_ERROR_UNEXPECTED; - } - nsString origin; - nsresult rv = GetOrigin(principal, origin); - if (NS_WARN_IF(NS_FAILED(rv))) { - callback->Done(); - return rv; - } - - rv = notificationStorage->Get(origin, mScope, mTag, callback); - if (NS_WARN_IF(NS_FAILED(rv))) { - callback->Done(); - return rv; - } - - return NS_OK; - } - - private: - ~WorkerGetRunnable() = default; -}; - -// static -already_AddRefed Notification::WorkerGet( - WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter, - const nsAString& aScope, ErrorResult& aRv) { - MOZ_ASSERT(aWorkerPrivate); - aWorkerPrivate->AssertIsOnWorkerThread(); - RefPtr p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - RefPtr proxy = - PromiseWorkerProxy::Create(aWorkerPrivate, p); - if (!proxy) { - aRv.Throw(NS_ERROR_DOM_ABORT_ERR); - return nullptr; - } - - RefPtr r = - new WorkerGetRunnable(proxy, aFilter.mTag, aScope); - // Since this is called from script via - // ServiceWorkerRegistration::GetNotifications, we can assert dispatch. - MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget())); - return p.forget(); -} - JSObject* Notification::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto); diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h index 7e0fa38a4983..a9147f28c788 100644 --- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -74,22 +74,6 @@ class Notification : public DOMEventTargetHelper, public SupportsWeakPtr { const GlobalObject& aGlobal, const nsAString& aTitle, const NotificationOptions& aOption, ErrorResult& aRv); - /** - * Used when dispatching the ServiceWorkerEvent. - * - * Does not initialize the Notification's behavior. - * This is because: - * 1) The Notification is not shown to the user and so the behavior - * parameters don't matter. - * 2) The default binding requires main thread for parsing the JSON from the - * string behavior. - */ - static Result, QMResult> ConstructFromFields( - nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle, - const nsAString& aDir, const nsAString& aLang, const nsAString& aBody, - const nsAString& aTag, const nsAString& aIcon, const nsAString& aData, - const nsAString& aServiceWorkerRegistrationScope); - /** * Used when retrieving notification objects from the parent process. */ @@ -124,15 +108,6 @@ class Notification : public DOMEventTargetHelper, public SupportsWeakPtr { static NotificationPermission GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv); - static already_AddRefed Get(nsPIDOMWindowInner* aWindow, - const GetNotificationOptions& aFilter, - const nsAString& aScope, - ErrorResult& aRv); - - static already_AddRefed WorkerGet( - WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter, - const nsAString& aScope, ErrorResult& aRv); - // Notification implementation of // ServiceWorkerRegistration.showNotification. // diff --git a/dom/notification/moz.build b/dom/notification/moz.build index 1b126db02e3a..99293f06dcb9 100644 --- a/dom/notification/moz.build +++ b/dom/notification/moz.build @@ -24,7 +24,7 @@ EXPORTS.mozilla.dom.notification += [ "IPCUtils.h", "NotificationChild.h", "NotificationParent.h", - "NotificationUtils.h", # Exported for ContentParent for now + "NotificationUtils.h", ] UNIFIED_SOURCES += [ diff --git a/dom/serviceworkers/PServiceWorkerRegistration.ipdl b/dom/serviceworkers/PServiceWorkerRegistration.ipdl index 64e406c5f3fe..3aea6bd5556e 100644 --- a/dom/serviceworkers/PServiceWorkerRegistration.ipdl +++ b/dom/serviceworkers/PServiceWorkerRegistration.ipdl @@ -5,6 +5,7 @@ include protocol PBackground; include ClientIPCTypes; +include DOMTypes; include IPCNavigationPreloadState; include IPCServiceWorkerRegistrationDescriptor; @@ -24,6 +25,7 @@ parent: async Unregister() returns (bool aSuccess, CopyableErrorResult aRv); async Update(nsCString aNewestWorkerScriptUrl) returns ( IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult aResult); + async GetNotifications(nsString aTag) returns (IPCNotificationsOrError aResult); // For NavigationPreload interface async SetNavigationPreloadEnabled(bool aEnabled) returns (bool aSuccess); diff --git a/dom/serviceworkers/ServiceWorkerRegistration.cpp b/dom/serviceworkers/ServiceWorkerRegistration.cpp index 75a21ba6498a..e1178dba5775 100644 --- a/dom/serviceworkers/ServiceWorkerRegistration.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp @@ -415,28 +415,85 @@ already_AddRefed ServiceWorkerRegistration::ShowNotification( return p.forget(); } +// https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-getnotifications already_AddRefed ServiceWorkerRegistration::GetNotifications( const GetNotificationOptions& aOptions, ErrorResult& aRv) { - nsIGlobalObject* global = GetParentObject(); + // Step 1: Let global be this’s relevant global object. + // Step 2: Let realm be this’s relevant Realm. + nsCOMPtr global = GetParentObject(); if (!global) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } - NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope()); + // Step 3: Let origin be this’s relevant settings object’s origin. + // (Done in ServiceWorkerRegistrationProxy::GetNotifications) - if (NS_IsMainThread()) { - nsCOMPtr window = do_QueryInterface(global); - if (NS_WARN_IF(!window)) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return nullptr; - } - return Notification::Get(window, aOptions, scope, aRv); + // Step 4: Let promise be a new promise in realm. + RefPtr promise = Promise::CreateInfallible(global); + + // Step 5: Run these steps in parallel: + // Step 5.1: Let tag be filter["tag"]. + // Step 5.2: Let notifications be a list of all notifications in the list of + // notifications whose origin is same origin with origin, whose service worker + // registration is this, and whose tag, if tag is not the empty string, is + // tag. + + if (!mActor) { + // While it's not clear from the current spec, it's fair to say that + // unregistered registrations cannot have a match in the step 5.2. See also + // bug 1881812. + // One could also say we should throw here, but no browsers throw. + promise->MaybeResolve(nsTArray>()); + return promise.forget(); } - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - worker->AssertIsOnWorkerThread(); - return Notification::WorkerGet(worker, aOptions, scope, aRv); + // Step 5.3: Queue a global task on the DOM manipulation task source + // given global to run these steps: + mActor->SendGetNotifications(aOptions.mTag) + ->Then(GetCurrentSerialEventTarget(), __func__, + [promise, scope = NS_ConvertUTF8toUTF16(mDescriptor.Scope())]( + const PServiceWorkerRegistrationChild:: + GetNotificationsPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsReject()) { + // An unregistered registration + promise->MaybeResolve(nsTArray>()); + return; + } + + if (aValue.ResolveValue().type() == + IPCNotificationsOrError::Tnsresult) { + // An active registration but had some internal error + promise->MaybeRejectWithInvalidStateError( + "Could not retrieve notifications"_ns); + return; + } + + const nsTArray& notifications = + aValue.ResolveValue().get_ArrayOfIPCNotification(); + + // Step 5.3.1: Let objects be a list. + nsTArray> objects(notifications.Length()); + + // Step 5.3.2: For each notification in notifications, in + // creation order, create a new Notification object with realm + // representing notification, and append it to objects. + for (const IPCNotification& ipcNotification : notifications) { + auto result = Notification::ConstructFromIPC( + promise->GetParentObject(), ipcNotification, scope); + if (result.isErr()) { + continue; + } + RefPtr n = result.unwrap(); + objects.AppendElement(n.forget()); + } + + // Step 5.3.3: Resolve promise with objects. + promise->MaybeResolve(std::move(objects)); + }); + + // Step 6: Return promise. + return promise.forget(); } void ServiceWorkerRegistration::SetNavigationPreloadEnabled( diff --git a/dom/serviceworkers/ServiceWorkerRegistrationParent.cpp b/dom/serviceworkers/ServiceWorkerRegistrationParent.cpp index 1c6ee60769cf..e8b9e2c68d96 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationParent.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistrationParent.cpp @@ -126,6 +126,23 @@ IPCResult ServiceWorkerRegistrationParent::RecvGetNavigationPreloadState( return IPC_OK(); } +IPCResult ServiceWorkerRegistrationParent::RecvGetNotifications( + const nsAString& aTag, GetNotificationsResolver&& aResolver) { + if (!mProxy) { + aResolver(NS_ERROR_DOM_INVALID_STATE_ERR); + return IPC_OK(); + } + + mProxy->GetNotifications(aTag)->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](const CopyableTArray& aNotifications) { + aResolver(aNotifications); + }, + [aResolver](nsresult) { aResolver(NS_ERROR_DOM_INVALID_STATE_ERR); }); + + return IPC_OK(); +} + ServiceWorkerRegistrationParent::ServiceWorkerRegistrationParent() : mDeleteSent(false) {} diff --git a/dom/serviceworkers/ServiceWorkerRegistrationParent.h b/dom/serviceworkers/ServiceWorkerRegistrationParent.h index a89730175f03..7e0ad50983fc 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationParent.h +++ b/dom/serviceworkers/ServiceWorkerRegistrationParent.h @@ -43,6 +43,9 @@ class ServiceWorkerRegistrationParent final mozilla::ipc::IPCResult RecvGetNavigationPreloadState( GetNavigationPreloadStateResolver&& aResolver) override; + mozilla::ipc::IPCResult RecvGetNotifications( + const nsAString& aTag, GetNotificationsResolver&& aResolver) override; + public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerRegistrationParent, override); diff --git a/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp b/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp index 1410b5616422..6ecbd8dc41c5 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp @@ -8,14 +8,17 @@ #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" +#include "mozilla/dom/notification/NotificationUtils.h" #include "mozilla/ipc/BackgroundParent.h" #include "ServiceWorkerManager.h" #include "ServiceWorkerRegistrationParent.h" #include "ServiceWorkerUnregisterCallback.h" +#include "nsINotificationStorage.h" namespace mozilla::dom { using mozilla::ipc::AssertIsOnBackgroundThread; +using namespace mozilla::dom::notification; class ServiceWorkerRegistrationProxy::DelayedUpdate final : public nsITimerCallback, @@ -484,4 +487,86 @@ ServiceWorkerRegistrationProxy::GetNavigationPreloadState() { return promise; } +// TODO(krosylight): Ideally this callback shouldn't be needed, see bug 1950116. +class NotificationsCallback : public nsINotificationStorageCallback { + public: + NS_DECL_ISUPPORTS + + already_AddRefed Promise() { + return mPromiseHolder.Ensure(__func__); + } + + NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle, + const nsAString& aDir, const nsAString& aLang, + const nsAString& aBody, const nsAString& aTag, + const nsAString& aIcon, const nsAString& aData, + const nsAString& aServiceWorkerRegistrationScope) final { + AssertIsOnMainThread(); + MOZ_ASSERT(!aID.IsEmpty()); + + NotificationDirection dir = + StringToEnum(aDir).valueOr( + NotificationDirection::Auto); + + // XXX(krosylight): we don't store all notification options + IPCNotification notification( + nsString(aID), + IPCNotificationOptions(nsString(aTitle), dir, nsString(aLang), + nsString(aBody), nsString(aTag), nsString(aIcon), + false, false, nsTArray(), + nsString(aData))); + + mNotifications.AppendElement(std::move(notification)); + return NS_OK; + } + + NS_IMETHOD Done() final { + mPromiseHolder.Resolve(std::move(mNotifications), __func__); + return NS_OK; + } + + protected: + virtual ~NotificationsCallback() = default; + + nsTArray mNotifications; + MozPromiseHolder mPromiseHolder; +}; + +NS_IMPL_ISUPPORTS(NotificationsCallback, nsINotificationStorageCallback) + +RefPtr ServiceWorkerRegistrationProxy::GetNotifications( + const nsAString& aTag) { + AssertIsOnBackgroundThread(); + + RefPtr self = this; + return InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [self, tag = nsString(aTag)]() { + Result, nsresult> principalResult = + self->mListeningClientInfo.GetPrincipal(); + if (principalResult.isErr()) { + return NotificationsPromise::CreateAndReject( + NS_ERROR_DOM_INVALID_STATE_ERR, __func__); + } + + nsCOMPtr principal = principalResult.unwrap(); + nsString origin; + GetOrigin(principal, origin); + + RefPtr callback = new NotificationsCallback(); + RefPtr promise = callback->Promise(); + + nsCOMPtr notificationStorage = + GetNotificationStorage( + self->mListeningClientInfo.IsPrivateBrowsing()); + + nsString scope; + self->GetScope(scope); + + notificationStorage->Get(origin, scope, tag, callback); + + return promise; + }); +} + } // namespace mozilla::dom diff --git a/dom/serviceworkers/ServiceWorkerRegistrationProxy.h b/dom/serviceworkers/ServiceWorkerRegistrationProxy.h index f0cc243e9b21..6960e1de4ce6 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.h +++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.h @@ -86,6 +86,8 @@ class ServiceWorkerRegistrationProxy final RefPtr GetNavigationPreloadState(); + RefPtr GetNotifications(const nsAString& aTag); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ServiceWorkerRegistrationProxy, override); }; diff --git a/dom/serviceworkers/ServiceWorkerUtils.h b/dom/serviceworkers/ServiceWorkerUtils.h index ad5598201ade..3e4d376a3813 100644 --- a/dom/serviceworkers/ServiceWorkerUtils.h +++ b/dom/serviceworkers/ServiceWorkerUtils.h @@ -7,6 +7,7 @@ #define _mozilla_dom_ServiceWorkerUtils_h #include "mozilla/MozPromise.h" +#include "mozilla/dom/DOMTypes.h" #include "mozilla/dom/IPCNavigationPreloadState.h" #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" #include "nsTArray.h" @@ -36,6 +37,9 @@ using ServiceWorkerRegistrationListPromise = using NavigationPreloadStatePromise = MozPromise; +using NotificationsPromise = + MozPromise, nsresult, false>; + using ServiceWorkerRegistrationCallback = std::function; diff --git a/testing/web-platform/tests/notifications/registration-association.https.window.js b/testing/web-platform/tests/notifications/registration-association.https.window.js index 53aed8d5bc89..e838257faa82 100644 --- a/testing/web-platform/tests/notifications/registration-association.https.window.js +++ b/testing/web-platform/tests/notifications/registration-association.https.window.js @@ -17,8 +17,8 @@ promise_test(async (t) => { await registration.showNotification("foo"); await registration.unregister(); - const newRegistration = await prepareActiveServiceWorker("noop-sw.js"); - const notifications = await newRegistration.getNotifications(); + registration = await prepareActiveServiceWorker("noop-sw.js"); + const notifications = await registration.getNotifications(); // The spec says notifications should be associated with service worker registration // and thus unregistering should dissociate the notification. @@ -29,3 +29,10 @@ promise_test(async (t) => { // > is not the empty string, is tag. assert_equals(notifications.length, 0, "Should return zero notification"); }, "A new SW registration gets no previous notification"); + +promise_test(async (t) => { + await registration.showNotification("foo"); + await registration.unregister(); + const notifications = await registration.getNotifications(); + assert_equals(notifications.length, 0, "Should return zero notification"); +}, "An unregistered SW registration gets no previous notification");