diff options
Diffstat (limited to 'dom/notification/Notification.h')
-rw-r--r-- | dom/notification/Notification.h | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h new file mode 100644 index 000000000..a2c4b5c68 --- /dev/null +++ b/dom/notification/Notification.h @@ -0,0 +1,471 @@ +/* -*- 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/workers/bindings/WorkerHolder.h" + +#include "nsIObserver.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsHashKeys.h" +#include "nsTHashtable.h" +#include "nsWeakReference.h" + +#define NOTIFICATIONTELEMETRYSERVICE_CONTRACTID \ + "@mozilla.org/notificationTelemetryService;1" + +class nsIPrincipal; +class nsIVariant; + +namespace mozilla { +namespace dom { + +class NotificationRef; +class WorkerNotificationObserver; +class Promise; + +namespace workers { + class WorkerPrivate; +} // namespace workers + +class Notification; +class NotificationWorkerHolder final : public workers::WorkerHolder +{ + // Since the feature is strongly held by a Notification, it is ok to hold + // a raw pointer here. + Notification* mNotification; + +public: + explicit NotificationWorkerHolder(Notification* aNotification); + + bool + Notify(workers::Status aStatus) override; +}; + +// Records telemetry probes at application startup, when a notification is +// shown, and when the notification permission is revoked for a site. +class NotificationTelemetryService final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + NotificationTelemetryService(); + + static already_AddRefed<NotificationTelemetryService> GetInstance(); + + nsresult Init(); + void RecordDNDSupported(); + void RecordPermissions(); + nsresult RecordSender(nsIPrincipal* aPrincipal); + +private: + virtual ~NotificationTelemetryService(); + + nsresult AddPermissionChangeObserver(); + nsresult RemovePermissionChangeObserver(); + + bool GetNotificationPermission(nsISupports* aSupports, + uint32_t* aCapability); + + bool mDNDRecorded; + nsTHashtable<nsStringHashKey> mOrigins; +}; + +/* + * 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; + friend class NotificationTelemetryService; + +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 RequireInteractionEnabled(JSContext* aCx, JSObject* aObj); + static bool PrefEnabled(JSContext* aCx, JSObject* aObj); + // Returns if Notification.get() is allowed for the current global. + static bool IsGetEnabled(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 already_AddRefed<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, + ErrorResult& aRv); + + 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> Get(const GlobalObject& aGlobal, + const GetNotificationOptions& aFilter, + ErrorResult& aRv); + + static already_AddRefed<Promise> WorkerGet(workers::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, + ErrorResult& aRv); + + void Close(); + + nsPIDOMWindowInner* GetParentObject() + { + return GetOwner(); + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + bool RequireInteraction() const; + + void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval); + + void InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData, ErrorResult& aRv); + + void InitFromBase64(const nsAString& aData, ErrorResult& aRv); + + 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. + workers::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(); + bool DispatchNotificationClickEvent(); + + static nsresult RemovePermission(nsIPrincipal* aPrincipal); + static nsresult OpenSettings(nsIPrincipal* aPrincipal); +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 aRequireNotification, + const NotificationBehavior& aBehavior); + + static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal, + const nsAString& aID, + const nsAString& aTitle, + const NotificationOptions& aOptions); + + nsresult Init(); + bool IsInPrivateBrowsing(); + void ShowInternal(); + void CloseInternal(); + + static NotificationPermission GetPermissionInternal(nsISupports* aGlobal, + ErrorResult& rv); + + static const nsString DirectionToString(NotificationDirection aDirection) + { + switch (aDirection) { + case NotificationDirection::Ltr: + return NS_LITERAL_STRING("ltr"); + case NotificationDirection::Rtl: + return NS_LITERAL_STRING("rtl"); + default: + return NS_LITERAL_STRING("auto"); + } + } + + 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) + { + workers::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; + 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 RegisterWorkerHolder(); + void UnregisterWorkerHolder(); + + nsresult ResolveIconAndSoundURL(nsString&, nsString&); + + // Only used for Notifications on Workers, worker thread only. + UniquePtr<NotificationWorkerHolder> mWorkerHolder; + // Target thread only. + uint32_t mTaskCount; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_notification_h__ + |