summaryrefslogtreecommitdiffstats
path: root/dom/notification/Notification.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/notification/Notification.h')
-rw-r--r--dom/notification/Notification.h360
1 files changed, 360 insertions, 0 deletions
diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h
new file mode 100644
index 0000000000..4ffa69cf49
--- /dev/null
+++ b/dom/notification/Notification.h
@@ -0,0 +1,360 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_notification_h__
+#define mozilla_dom_notification_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/NotificationBinding.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "nsIObserver.h"
+#include "nsISupports.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWeakReference.h"
+
+class nsIPrincipal;
+class nsIVariant;
+
+namespace mozilla::dom {
+
+class NotificationRef;
+class WorkerNotificationObserver;
+class Promise;
+class StrongWorkerRef;
+
+/*
+ * Notifications on workers introduce some lifetime issues. The property we
+ * are trying to satisfy is:
+ * Whenever a task is dispatched to the main thread to operate on
+ * a Notification, the Notification should be addrefed on the worker thread
+ * and a feature should be added to observe the worker lifetime. This main
+ * thread owner should ensure it properly releases the reference to the
+ * Notification, additionally removing the feature if necessary.
+ *
+ * To enforce the correct addref and release, along with managing the feature,
+ * we introduce a NotificationRef. Only one object may ever own
+ * a NotificationRef, so UniquePtr<> is used throughout. The NotificationRef
+ * constructor calls AddRefObject(). When it is destroyed (on any thread) it
+ * releases the Notification on the correct thread.
+ *
+ * Code should only access the underlying Notification object when it can
+ * guarantee that it retains ownership of the NotificationRef while doing so.
+ *
+ * The one kink in this mechanism is that the worker feature may be Notify()ed
+ * if the worker stops running script, even if the Notification's corresponding
+ * UI is still visible to the user. We handle this case with the following
+ * steps:
+ * a) Close the notification. This is done by blocking the worker on the main
+ * thread. This ensures that there are no main thread holders when the worker
+ * resumes. This also deals with the case where Notify() runs on the worker
+ * before the observer has been created on the main thread. Even in such
+ * a situation, the CloseNotificationRunnable() will only run after the
+ * Show task that was previously queued. Since the show task is only queued
+ * once when the Notification is created, we can be sure that no new tasks
+ * will follow the Notify().
+ *
+ * b) Ask the observer to let go of its NotificationRef's underlying
+ * Notification without proper cleanup since the feature will handle the
+ * release. This is only OK because every notification has only one
+ * associated observer. The NotificationRef itself is still owned by the
+ * observer and deleted by the UniquePtr, but it doesn't do anything since
+ * the underlying Notification is null.
+ *
+ * To unify code-paths, we use the same NotificationRef in the main
+ * thread implementation too.
+ *
+ * Note that the Notification's JS wrapper does it's standard
+ * AddRef()/Release() and is not affected by any of this.
+ *
+ * Since the worker event queue can have runnables that will dispatch events on
+ * the Notification, the NotificationRef destructor will first try to release
+ * the Notification by dispatching a normal runnable to the worker so that it is
+ * queued after any event runnables. If that dispatch fails, it means the worker
+ * is no longer running and queued WorkerRunnables will be canceled, so we
+ * dispatch a control runnable instead.
+ *
+ */
+class Notification : public DOMEventTargetHelper,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ friend class CloseNotificationRunnable;
+ friend class NotificationTask;
+ friend class NotificationPermissionRequest;
+ friend class MainThreadNotificationObserver;
+ friend class NotificationStorageCallback;
+ friend class ServiceWorkerNotificationObserver;
+ friend class WorkerGetRunnable;
+ friend class WorkerNotificationObserver;
+
+ public:
+ IMPL_EVENT_HANDLER(click)
+ IMPL_EVENT_HANDLER(show)
+ IMPL_EVENT_HANDLER(error)
+ IMPL_EVENT_HANDLER(close)
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Notification,
+ DOMEventTargetHelper)
+ NS_DECL_NSIOBSERVER
+
+ static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
+
+ static already_AddRefed<Notification> Constructor(
+ 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<already_AddRefed<Notification>, 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);
+
+ void GetID(nsAString& aRetval) { aRetval = mID; }
+
+ void GetTitle(nsAString& aRetval) { aRetval = mTitle; }
+
+ NotificationDirection Dir() { return mDir; }
+
+ void GetLang(nsAString& aRetval) { aRetval = mLang; }
+
+ void GetBody(nsAString& aRetval) { aRetval = mBody; }
+
+ void GetTag(nsAString& aRetval) { aRetval = mTag; }
+
+ void GetIcon(nsAString& aRetval) { aRetval = mIconUrl; }
+
+ void SetStoredState(bool val) { mIsStored = val; }
+
+ bool IsStored() { return mIsStored; }
+
+ static bool RequestPermissionEnabledForScope(JSContext* aCx,
+ JSObject* /* unused */);
+
+ static already_AddRefed<Promise> RequestPermission(
+ const GlobalObject& aGlobal,
+ const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
+ ErrorResult& aRv);
+
+ static NotificationPermission GetPermission(const GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise> Get(nsPIDOMWindowInner* aWindow,
+ const GetNotificationOptions& aFilter,
+ const nsAString& aScope,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Promise> WorkerGet(
+ WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
+ const nsAString& aScope, ErrorResult& aRv);
+
+ // Notification implementation of
+ // ServiceWorkerRegistration.showNotification.
+ //
+ //
+ // Note that aCx may not be in the compartment of aGlobal, but aOptions will
+ // have its JS things in the compartment of aCx.
+ static already_AddRefed<Promise> ShowPersistentNotification(
+ JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
+ const nsAString& aTitle, const NotificationOptions& aOptions,
+ const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv);
+
+ void Close();
+
+ nsPIDOMWindowInner* GetParentObject() { return GetOwner(); }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool RequireInteraction() const;
+
+ bool Silent() const;
+
+ void GetVibrate(nsTArray<uint32_t>& aRetval) const;
+
+ void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);
+
+ void InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
+ ErrorResult& aRv);
+
+ Result<Ok, QMResult> InitFromBase64(const nsAString& aData);
+
+ void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }
+
+ // Initialized on the worker thread, never unset, and always used in
+ // a read-only capacity. Used on any thread.
+ CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate;
+
+ // Main thread only.
+ WorkerNotificationObserver* mObserver;
+
+ // The NotificationTask calls ShowInternal()/CloseInternal() on the
+ // Notification. At this point the task has ownership of the Notification. It
+ // passes this on to the Notification itself via mTempRef so that
+ // ShowInternal()/CloseInternal() may pass it along appropriately (or release
+ // it).
+ //
+ // Main thread only.
+ UniquePtr<NotificationRef> mTempRef;
+
+ // Returns true if addref succeeded.
+ bool AddRefObject();
+ void ReleaseObject();
+
+ static NotificationPermission GetPermission(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv);
+
+ static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
+ ErrorResult& rv);
+
+ static NotificationPermission TestPermission(nsIPrincipal* aPrincipal);
+
+ bool DispatchClickEvent();
+
+ static nsresult RemovePermission(nsIPrincipal* aPrincipal);
+ static nsresult OpenSettings(nsIPrincipal* aPrincipal);
+
+ nsresult DispatchToMainThread(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ protected:
+ Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
+ const nsAString& aTitle, const nsAString& aBody,
+ NotificationDirection aDir, const nsAString& aLang,
+ const nsAString& aTag, const nsAString& aIconUrl,
+ bool aRequireInteraction, bool aSilent,
+ nsTArray<uint32_t>&& aVibrate,
+ const NotificationBehavior& aBehavior);
+
+ static already_AddRefed<Notification> CreateInternal(
+ nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
+ const NotificationOptions& aOptions, ErrorResult& aRv);
+
+ nsresult Init();
+ bool IsInPrivateBrowsing();
+ void ShowInternal();
+ void CloseInternal(bool aContextClosed = false);
+
+ static NotificationPermission GetPermissionInternal(
+ nsPIDOMWindowInner* aWindow, ErrorResult& rv);
+
+ static const nsString DirectionToString(NotificationDirection aDirection) {
+ switch (aDirection) {
+ case NotificationDirection::Ltr:
+ return u"ltr"_ns;
+ case NotificationDirection::Rtl:
+ return u"rtl"_ns;
+ default:
+ return u"auto"_ns;
+ }
+ }
+
+ static NotificationDirection StringToDirection(const nsAString& aDirection) {
+ if (aDirection.EqualsLiteral("ltr")) {
+ return NotificationDirection::Ltr;
+ }
+ if (aDirection.EqualsLiteral("rtl")) {
+ return NotificationDirection::Rtl;
+ }
+ return NotificationDirection::Auto;
+ }
+
+ static nsresult GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin);
+
+ void GetAlertName(nsAString& aRetval) {
+ AssertIsOnMainThread();
+ if (mAlertName.IsEmpty()) {
+ SetAlertName();
+ }
+ aRetval = mAlertName;
+ }
+
+ void GetScope(nsAString& aScope) { aScope = mScope; }
+
+ void SetScope(const nsAString& aScope) {
+ MOZ_ASSERT(mScope.IsEmpty());
+ mScope = aScope;
+ }
+
+ const nsString mID;
+ const nsString mTitle;
+ const nsString mBody;
+ const NotificationDirection mDir;
+ const nsString mLang;
+ const nsString mTag;
+ const nsString mIconUrl;
+ const bool mRequireInteraction;
+ const bool mSilent;
+ nsTArray<uint32_t> mVibrate;
+ nsString mDataAsBase64;
+ const NotificationBehavior mBehavior;
+
+ // It's null until GetData is first called
+ JS::Heap<JS::Value> mData;
+
+ nsString mAlertName;
+ nsString mScope;
+
+ // Main thread only.
+ bool mIsClosed;
+
+ // We need to make a distinction between the notification being closed i.e.
+ // removed from any pending or active lists, and the notification being
+ // removed from the database. NotificationDB might fail when trying to remove
+ // the notification.
+ bool mIsStored;
+
+ static uint32_t sCount;
+
+ private:
+ virtual ~Notification();
+
+ // Creates a Notification and shows it. Returns a reference to the
+ // Notification if result is NS_OK. The lifetime of this Notification is tied
+ // to an underlying NotificationRef. Do not hold a non-stack raw pointer to
+ // it. Be careful about thread safety if acquiring a strong reference.
+ //
+ // Note that aCx may not be in the compartment of aGlobal, but aOptions will
+ // have its JS things in the compartment of aCx.
+ static already_AddRefed<Notification> CreateAndShow(
+ JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
+ const NotificationOptions& aOptions, const nsAString& aScope,
+ ErrorResult& aRv);
+
+ nsIPrincipal* GetPrincipal();
+
+ nsresult PersistNotification();
+ void UnpersistNotification();
+
+ void SetAlertName();
+
+ bool IsTargetThread() const { return NS_IsMainThread() == !mWorkerPrivate; }
+
+ bool CreateWorkerRef();
+
+ nsresult ResolveIconAndSoundURL(nsString&, nsString&);
+
+ // Only used for Notifications on Workers, worker thread only.
+ RefPtr<StrongWorkerRef> mWorkerRef;
+ // Target thread only.
+ uint32_t mTaskCount;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_notification_h__