summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/ServiceWorkerPrivate.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/serviceworkers/ServiceWorkerPrivate.h305
1 files changed, 305 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerPrivate.h b/dom/serviceworkers/ServiceWorkerPrivate.h
new file mode 100644
index 0000000000..528baad17d
--- /dev/null
+++ b/dom/serviceworkers/ServiceWorkerPrivate.h
@@ -0,0 +1,305 @@
+/* -*- 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_serviceworkerprivate_h
+#define mozilla_dom_serviceworkerprivate_h
+
+#include <type_traits>
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/MozPromise.h"
+
+#define NOTIFICATION_CLICK_EVENT_NAME u"notificationclick"
+#define NOTIFICATION_CLOSE_EVENT_NAME u"notificationclose"
+
+class nsIInterceptedChannel;
+class nsIWorkerDebugger;
+
+namespace mozilla {
+
+class JSObjectHolder;
+
+namespace dom {
+
+class ClientInfoAndState;
+class ServiceWorkerCloneData;
+class ServiceWorkerInfo;
+class ServiceWorkerPrivate;
+class ServiceWorkerPrivateImpl;
+class ServiceWorkerRegistrationInfo;
+
+namespace ipc {
+class StructuredCloneData;
+} // namespace ipc
+
+class LifeCycleEventCallback : public Runnable {
+ public:
+ LifeCycleEventCallback() : Runnable("dom::LifeCycleEventCallback") {}
+
+ // Called on the worker thread.
+ virtual void SetResult(bool aResult) = 0;
+};
+
+// Used to keep track of pending waitUntil as well as in-flight extendable
+// events. When the last token is released, we attempt to terminate the worker.
+class KeepAliveToken final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate);
+
+ private:
+ ~KeepAliveToken();
+
+ RefPtr<ServiceWorkerPrivate> mPrivate;
+};
+
+// ServiceWorkerPrivate is a wrapper for managing the on-demand aspect of
+// service workers. It handles all event dispatching to the worker and ensures
+// the worker thread is running when needed.
+//
+// Lifetime management: To spin up the worker thread we own a |WorkerPrivate|
+// object which can be cancelled if no events are received for a certain
+// amount of time. The worker is kept alive by holding a |KeepAliveToken|
+// reference.
+//
+// Extendable events hold tokens for the duration of their handler execution
+// and until their waitUntil promise is resolved, while ServiceWorkerPrivate
+// will hold a token for |dom.serviceWorkers.idle_timeout| seconds after each
+// new event.
+//
+// Note: All timer events must be handled on the main thread because the
+// worker may block indefinitely the worker thread (e. g. infinite loop in the
+// script).
+//
+// There are 3 cases where we may ignore keep alive tokens:
+// 1. When ServiceWorkerPrivate's token expired, if there are still waitUntil
+// handlers holding tokens, we wait another
+// |dom.serviceWorkers.idle_extended_timeout| seconds before forcibly
+// terminating the worker.
+// 2. If the worker stopped controlling documents and it is not handling push
+// events.
+// 3. The content process is shutting down.
+//
+// Adding an API function for a new event requires calling |SpawnWorkerIfNeeded|
+// with an appropriate reason before any runnable is dispatched to the worker.
+// If the event is extendable then the runnable should inherit
+// ExtendableEventWorkerRunnable.
+class ServiceWorkerPrivate final {
+ friend class KeepAliveToken;
+ friend class ServiceWorkerPrivateImpl;
+
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ServiceWorkerPrivate)
+
+ using HasThreadSafeRefCnt = std::false_type;
+
+ protected:
+ nsCycleCollectingAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ public:
+ // TODO: remove this class. There's one (and only should be one) concrete
+ // class that derives this abstract base class.
+ class Inner {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual nsresult SendMessageEvent(
+ RefPtr<ServiceWorkerCloneData>&& aData,
+ const ClientInfoAndState& aClientInfoAndState) = 0;
+
+ virtual nsresult CheckScriptEvaluation(
+ RefPtr<LifeCycleEventCallback> aScriptEvaluationCallback) = 0;
+
+ virtual nsresult SendLifeCycleEvent(
+ const nsAString& aEventName,
+ RefPtr<LifeCycleEventCallback> aCallback) = 0;
+
+ virtual nsresult SendPushEvent(
+ RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
+ const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) = 0;
+
+ virtual nsresult SendPushSubscriptionChangeEvent() = 0;
+
+ virtual nsresult SendNotificationEvent(
+ const nsAString& aEventName, 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& aBehavior,
+ const nsAString& aScope, uint32_t aDisableOpenClickDelay) = 0;
+
+ virtual nsresult SendFetchEvent(
+ RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
+ nsCOMPtr<nsIInterceptedChannel> aChannel, const nsAString& aClientId,
+ const nsAString& aResultingClientId) = 0;
+
+ virtual nsresult SpawnWorkerIfNeeded() = 0;
+
+ virtual void TerminateWorker() = 0;
+
+ virtual void UpdateState(ServiceWorkerState aState) = 0;
+
+ virtual void NoteDeadOuter() = 0;
+
+ virtual bool WorkerIsDead() const = 0;
+ };
+
+ explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
+
+ nsresult SendMessageEvent(RefPtr<ServiceWorkerCloneData>&& aData,
+ const ClientInfoAndState& aClientInfoAndState);
+
+ // This is used to validate the worker script and continue the installation
+ // process.
+ nsresult CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
+
+ nsresult SendLifeCycleEvent(const nsAString& aEventType,
+ LifeCycleEventCallback* aCallback);
+
+ nsresult SendPushEvent(const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData,
+ ServiceWorkerRegistrationInfo* aRegistration);
+
+ nsresult SendPushSubscriptionChangeEvent();
+
+ nsresult SendNotificationEvent(const nsAString& aEventName,
+ 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& aBehavior,
+ const nsAString& aScope);
+
+ nsresult SendFetchEvent(nsIInterceptedChannel* aChannel,
+ nsILoadGroup* aLoadGroup, const nsAString& aClientId,
+ const nsAString& aResultingClientId);
+
+ bool MaybeStoreISupports(nsISupports* aSupports);
+
+ void RemoveISupports(nsISupports* aSupports);
+
+ // This will terminate the current running worker thread and drop the
+ // workerPrivate reference.
+ // Called by ServiceWorkerInfo when [[Clear Registration]] is invoked
+ // or whenever the spec mandates that we terminate the worker.
+ // This is a no-op if the worker has already been stopped.
+ void TerminateWorker();
+
+ void NoteDeadServiceWorkerInfo();
+
+ void NoteStoppedControllingDocuments();
+
+ void UpdateState(ServiceWorkerState aState);
+
+ nsresult GetDebugger(nsIWorkerDebugger** aResult);
+
+ nsresult AttachDebugger();
+
+ nsresult DetachDebugger();
+
+ bool IsIdle() const;
+
+ // This promise is used schedule clearing of the owning registrations and its
+ // associated Service Workers if that registration becomes "unreachable" by
+ // the ServiceWorkerManager. This occurs under two conditions, which are the
+ // preconditions to calling this method:
+ // - The owning registration must be unregistered.
+ // - The associated Service Worker must *not* be controlling clients.
+ //
+ // Additionally, perhaps stating the obvious, the associated Service Worker
+ // must *not* be idle (whatever must be done "when idle" can just be done
+ // immediately).
+ RefPtr<GenericPromise> GetIdlePromise();
+
+ void SetHandlesFetch(bool aValue);
+
+ private:
+ enum WakeUpReason {
+ FetchEvent = 0,
+ PushEvent,
+ PushSubscriptionChangeEvent,
+ MessageEvent,
+ NotificationClickEvent,
+ NotificationCloseEvent,
+ LifeCycleEvent,
+ AttachEvent,
+ Unknown
+ };
+
+ // Timer callbacks
+ void NoteIdleWorkerCallback(nsITimer* aTimer);
+
+ void TerminateWorkerCallback(nsITimer* aTimer);
+
+ void RenewKeepAliveToken(WakeUpReason aWhy);
+
+ void ResetIdleTimeout();
+
+ void AddToken();
+
+ void ReleaseToken();
+
+ nsresult SpawnWorkerIfNeeded(WakeUpReason aWhy,
+ bool* aNewWorkerCreated = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr);
+
+ ~ServiceWorkerPrivate();
+
+ already_AddRefed<KeepAliveToken> CreateEventKeepAliveToken();
+
+ // The info object owns us. It is possible to outlive it for a brief period
+ // of time if there are pending waitUntil promises, in which case it
+ // will be null and |SpawnWorkerIfNeeded| will always fail.
+ ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo;
+
+ // The WorkerPrivate object can only be closed by this class or by the
+ // RuntimeService class if gecko is shutting down. Closing the worker
+ // multiple times is OK, since the second attempt will be a no-op.
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+
+ nsCOMPtr<nsITimer> mIdleWorkerTimer;
+
+ // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the
+ // worker a grace period after each event.
+ RefPtr<KeepAliveToken> mIdleKeepAliveToken;
+
+ uint64_t mDebuggerCount;
+
+ uint64_t mTokenCount;
+
+ // Meant for keeping objects alive while handling requests from the worker
+ // on the main thread. Access to this array is provided through
+ // |StoreISupports| and |RemoveISupports|. Note that the array is also
+ // cleared whenever the worker is terminated.
+ nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
+
+ // Array of function event worker runnables that are pending due to
+ // the worker activating. Main thread only.
+ nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents;
+
+ RefPtr<Inner> mInner;
+
+ // Used by the owning `ServiceWorkerRegistrationInfo` when it wants to call
+ // `Clear` after being unregistered and isn't controlling any clients but this
+ // worker (i.e. the registration's active worker) isn't idle yet. Note that
+ // such an event should happen at most once in a
+ // `ServiceWorkerRegistrationInfo`s lifetime, so this promise should also only
+ // be obtained at most once.
+ MozPromiseHolder<GenericPromise> mIdlePromiseHolder;
+
+#ifdef DEBUG
+ bool mIdlePromiseObtained = false;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_serviceworkerprivate_h