summaryrefslogtreecommitdiffstats
path: root/dom/workers/WorkerRunnable.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/workers/WorkerRunnable.h508
1 files changed, 508 insertions, 0 deletions
diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h
new file mode 100644
index 0000000000..34a90e3c39
--- /dev/null
+++ b/dom/workers/WorkerRunnable.h
@@ -0,0 +1,508 @@
+/* -*- 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_workers_workerrunnable_h__
+#define mozilla_dom_workers_workerrunnable_h__
+
+#include <cstdint>
+#include <utility>
+#include "MainThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerStatus.h"
+#include "nsCOMPtr.h"
+#include "nsICancelableRunnable.h"
+#include "nsIRunnable.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+
+struct JSContext;
+class nsIEventTarget;
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class WorkerPrivate;
+
+// Use this runnable to communicate from the worker to its parent or vice-versa.
+// The busy count must be taken into consideration and declared at construction
+// time.
+class WorkerRunnable : public nsIRunnable, public nsICancelableRunnable {
+ public:
+ enum TargetAndBusyBehavior {
+ // Target the main thread for top-level workers, otherwise target the
+ // WorkerThread of the worker's parent. No change to the busy count.
+ ParentThreadUnchangedBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // be incremented before dispatching and decremented (asynchronously) after
+ // running.
+ WorkerThreadModifyBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // not be modified in any way. Besides worker-internal runnables this is
+ // almost always the wrong choice.
+ WorkerThreadUnchangedBusyCount
+ };
+
+ protected:
+ // The WorkerPrivate that this runnable is associated with.
+ WorkerPrivate* mWorkerPrivate;
+
+ // See above.
+ TargetAndBusyBehavior mBehavior;
+
+ // It's unclear whether or not Cancel() is supposed to work when called on any
+ // thread. To be safe we're using an atomic but it's likely overkill.
+ Atomic<uint32_t> mCanceled;
+
+ private:
+ // Whether or not Cancel() is currently being called from inside the Run()
+ // method. Avoids infinite recursion when a subclass calls Run() from inside
+ // Cancel(). Only checked and modified on the target thread.
+ bool mCallingCancelWithinRun;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // If you override Cancel() then you'll need to either call the base class
+ // Cancel() method or override IsCanceled() so that the Run() method bails out
+ // appropriately.
+ // Cancel() should not be called more than once and we throw
+ // NS_ERROR_UNEXPECTED if it is. If you override it, ensure to call the base
+ // class method first and bail out on failure to avoid unexpected side
+ // effects.
+ nsresult Cancel() override;
+
+ // The return value is true if and only if both PreDispatch and
+ // DispatchInternal return true.
+ bool Dispatch();
+
+ // See above note about Cancel().
+ // TODO: Check if we can remove the possibility to override IsCanceled.
+ virtual bool IsCanceled() const { return mCanceled != 0; }
+
+ // True if this runnable is handled by running JavaScript in some global that
+ // could possibly be a debuggee, and thus needs to be deferred when the target
+ // is paused in the debugger, until the JavaScript invocation in progress has
+ // run to completion. Examples are MessageEventRunnable and
+ // ReportErrorRunnable. These runnables are segregated into separate
+ // ThrottledEventQueues, which the debugger pauses.
+ //
+ // Note that debugger runnables do not fall in this category, since we don't
+ // support debugging the debugger server at the moment.
+ virtual bool IsDebuggeeRunnable() const { return false; }
+
+ static WorkerRunnable* FromRunnable(nsIRunnable* aRunnable);
+
+ protected:
+ WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = WorkerThreadModifyBusyCount)
+#ifdef DEBUG
+ ;
+#else
+ : mWorkerPrivate(aWorkerPrivate),
+ mBehavior(aBehavior),
+ mCanceled(0),
+ mCallingCancelWithinRun(false) {
+ }
+#endif
+
+ // This class is reference counted.
+ virtual ~WorkerRunnable() = default;
+
+ // Returns true if this runnable should be dispatched to the debugger queue,
+ // and false otherwise.
+ virtual bool IsDebuggerRunnable() const;
+
+ nsIGlobalObject* DefaultGlobalObject() const;
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ // Also increments the busy count of |mWorkerPrivate| if targeting the
+ // WorkerThread.
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate);
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult);
+
+ // May be implemented by subclasses if desired if they need to do some sort of
+ // setup before we try to set up our JSContext and compartment for real.
+ // Typically the only thing that should go in here is creation of the worker's
+ // global.
+ //
+ // If false is returned, WorkerRun will not be called at all. PostRun will
+ // still be called, with false passed for aRunResult.
+ virtual bool PreRun(WorkerPrivate* aWorkerPrivate);
+
+ // Must be implemented by subclasses. Called on the target thread. The return
+ // value will be passed to PostRun(). The JSContext passed in here comes from
+ // an AutoJSAPI (or AutoEntryScript) that we set up on the stack. If
+ // mBehavior is ParentThreadUnchangedBusyCount, it is in the compartment of
+ // mWorkerPrivate's reflector (i.e. the worker object in the parent thread),
+ // unless that reflector is null, in which case it's in the compartment of the
+ // parent global (which is the compartment reflector would have been in), or
+ // in the null compartment if there is no parent global. For other mBehavior
+ // values, we're running on the worker thread and aCx is in whatever
+ // compartment GetCurrentWorkerThreadJSContext() was in when
+ // nsIRunnable::Run() got called. This is actually important for cases when a
+ // runnable spins a syncloop and wants everything that happens during the
+ // syncloop to happen in the compartment that runnable set up (which may, for
+ // example, be a debugger sandbox compartment!). If aCx wasn't in a
+ // compartment to start with, aCx will be in either the debugger global's
+ // compartment or the worker's global's compartment depending on whether
+ // IsDebuggerRunnable() is true.
+ //
+ // Immediately after WorkerRun returns, the caller will assert that either it
+ // returns false or there is no exception pending on aCx. Then it will report
+ // any pending exceptions on aCx.
+ virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;
+
+ // By default asserts that Run() (and WorkerRun()) were called on the correct
+ // thread. Also sends an asynchronous message to the ParentThread if the
+ // busy count was previously modified in PreDispatch().
+ //
+ // The aCx passed here is the same one as was passed to WorkerRun and is
+ // still in the same compartment. PostRun implementations must NOT leave an
+ // exception on the JSContext and must not run script, because the incoming
+ // JSContext may be in the null compartment.
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult);
+
+ virtual bool DispatchInternal();
+
+ // Calling Run() directly is not supported. Just call Dispatch() and
+ // WorkerRun() will be called on the correct thread automatically.
+ NS_DECL_NSIRUNNABLE
+};
+
+// This runnable is used to send a message to a worker debugger.
+class WorkerDebuggerRunnable : public WorkerRunnable {
+ protected:
+ explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ virtual ~WorkerDebuggerRunnable() = default;
+
+ private:
+ virtual bool IsDebuggerRunnable() const override { return true; }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) final {
+ AssertIsOnMainThread();
+
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+};
+
+// This runnable is used to send a message directly to a worker's sync loop.
+class WorkerSyncRunnable : public WorkerRunnable {
+ protected:
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget);
+
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget);
+
+ virtual ~WorkerSyncRunnable();
+
+ virtual bool DispatchInternal() override;
+};
+
+// This runnable is identical to WorkerSyncRunnable except it is meant to be
+// created on and dispatched from the main thread only. Its WorkerRun/PostRun
+// will run on the worker thread.
+class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable {
+ protected:
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget) {
+ AssertIsOnMainThread();
+ }
+
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, std::move(aSyncLoopTarget)) {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerSyncRunnable() = default;
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+};
+
+// This runnable is processed as soon as it is received by the worker,
+// potentially running before previously queued runnables and perhaps even with
+// other JS code executing on the stack. These runnables must not alter the
+// state of the JS runtime and should only twiddle state values. The busy count
+// is never modified.
+class WorkerControlRunnable : public WorkerRunnable {
+ friend class WorkerPrivate;
+
+ protected:
+ WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+#ifdef DEBUG
+ ;
+#else
+ : WorkerRunnable(aWorkerPrivate, aBehavior) {
+ }
+#endif
+
+ virtual ~WorkerControlRunnable() = default;
+
+ nsresult Cancel() override;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerControlRunnable, WorkerRunnable)
+
+ private:
+ virtual bool DispatchInternal() override;
+
+ // Should only be called by WorkerPrivate::DoRunLoop.
+ using WorkerRunnable::Cancel;
+};
+
+// A convenience class for WorkerRunnables that are originated on the main
+// thread.
+class MainThreadWorkerRunnable : public WorkerRunnable {
+ protected:
+ explicit MainThreadWorkerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ AssertIsOnMainThread();
+ }
+};
+
+// A convenience class for WorkerControlRunnables that originate on the main
+// thread.
+class MainThreadWorkerControlRunnable : public WorkerControlRunnable {
+ protected:
+ explicit MainThreadWorkerControlRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ virtual ~MainThreadWorkerControlRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ AssertIsOnMainThread();
+ }
+};
+
+// A WorkerRunnable that should be dispatched from the worker to itself for
+// async tasks. This will increment the busy count PostDispatch() (only if
+// dispatch was successful) and decrement it in PostRun().
+//
+// Async tasks will almost always want to use this since
+// a WorkerSameThreadRunnable keeps the Worker from being GCed.
+class WorkerSameThreadRunnable : public WorkerRunnable {
+ protected:
+ explicit WorkerSameThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) {}
+
+ virtual ~WorkerSameThreadRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ // We just delegate PostRun to WorkerRunnable, since it does exactly
+ // what we want.
+};
+
+// Base class for the runnable objects, which makes a synchronous call to
+// dispatch the tasks from the worker thread to the main thread.
+//
+// Note that the derived class must override MainThreadRun.
+class WorkerMainThreadRunnable : public Runnable {
+ protected:
+ WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsISerialEventTarget> mSyncLoopTarget;
+ const nsCString mTelemetryKey;
+
+ explicit WorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey);
+ ~WorkerMainThreadRunnable();
+
+ virtual bool MainThreadRun() = 0;
+
+ public:
+ // Dispatch the runnable to the main thread. If dispatch to main thread
+ // fails, or if the worker is in a state equal or greater of aFailStatus, an
+ // error will be reported on aRv. Normally you want to use 'Canceling' for
+ // aFailStatus, except if you want an infallible runnable. In this case, use
+ // 'Killing'.
+ // In that case the error MUST be propagated out to script.
+ void Dispatch(WorkerStatus aFailStatus, ErrorResult& aRv);
+
+ private:
+ NS_IMETHOD Run() override;
+};
+
+// This runnable is an helper class for dispatching something from a worker
+// thread to the main-thread and back to the worker-thread. During this
+// operation, this class will keep the worker alive.
+// The purpose of RunBackOnWorkerThreadForCleanup() must be used, as the name
+// says, only to release resources, no JS has to be executed, no timers, or
+// other things. The reason of such limitations is that, in order to execute
+// this method in any condition (also when the worker is shutting down), a
+// Control Runnable is used, and, this could generate a reordering of existing
+// runnables.
+class WorkerProxyToMainThreadRunnable : public Runnable {
+ protected:
+ WorkerProxyToMainThreadRunnable();
+
+ virtual ~WorkerProxyToMainThreadRunnable();
+
+ // First this method is called on the main-thread.
+ virtual void RunOnMainThread(WorkerPrivate* aWorkerPrivate) = 0;
+
+ // After this second method is called on the worker-thread.
+ virtual void RunBackOnWorkerThreadForCleanup(
+ WorkerPrivate* aWorkerPrivate) = 0;
+
+ public:
+ bool Dispatch(WorkerPrivate* aWorkerPrivate);
+
+ virtual bool ForMessaging() const { return false; }
+
+ private:
+ NS_IMETHOD Run() override;
+
+ void PostDispatchOnMainThread();
+
+ void ReleaseWorker();
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+// This runnable is used to stop a sync loop and it's meant to be used on the
+// main-thread only. As sync loops keep the busy count incremented as long as
+// they run this runnable does not modify the busy count
+// in any way.
+class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable {
+ nsresult mResult;
+
+ public:
+ // Passing null for aSyncLoopTarget is not allowed.
+ MainThreadStopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ nsresult aResult);
+
+ // By default StopSyncLoopRunnables cannot be canceled since they could leave
+ // a sync loop spinning forever.
+ nsresult Cancel() override;
+
+ protected:
+ virtual ~MainThreadStopSyncLoopRunnable() = default;
+
+ private:
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) final {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override;
+
+ bool DispatchInternal() final;
+};
+
+// Runnables handled by content JavaScript (MessageEventRunnable, JavaScript
+// error reports, and so on) must not be delivered while that content is in the
+// midst of being debugged; the debuggee must be allowed to complete its current
+// JavaScript invocation and return to its own event loop. Only then is it
+// prepared for messages sent from the worker.
+//
+// Runnables that need to be deferred in this way should inherit from this
+// class. They will be routed to mMainThreadDebuggeeEventTarget, which is paused
+// while the window is suspended, as it is whenever the debugger spins its
+// nested event loop. When the debugger leaves its nested event loop, it resumes
+// the window, so that mMainThreadDebuggeeEventTarget will resume delivering
+// runnables from the worker when control returns to the main event loop.
+//
+// When a page enters the bfcache, it freezes all its workers. Since a frozen
+// worker processes only control runnables, it doesn't take any special
+// consideration to prevent WorkerDebuggeeRunnables sent from child to parent
+// workers from running; they'll never run anyway. But WorkerDebuggeeRunnables
+// from a top-level frozen worker to its parent window must not be delivered
+// either, even as the main thread event loop continues to spin. Thus, freezing
+// a top-level worker also pauses mMainThreadDebuggeeEventTarget.
+class WorkerDebuggeeRunnable : public WorkerRunnable {
+ protected:
+ WorkerDebuggeeRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = ParentThreadUnchangedBusyCount)
+ : WorkerRunnable(aWorkerPrivate, aBehavior) {}
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ private:
+ // This override is deliberately private: it doesn't make sense to call it if
+ // we know statically that we are a WorkerDebuggeeRunnable.
+ bool IsDebuggeeRunnable() const override { return true; }
+
+ // Runnables sent upwards, to the content window or parent worker, must keep
+ // their sender alive until they are delivered: they check back with the
+ // sender in case it has been terminated after having dispatched the runnable
+ // (in which case it should not be acted upon); and runnables sent to content
+ // wait until delivery to determine the target window, since
+ // WorkerPrivate::GetWindow may only be used on the main thread.
+ //
+ // Runnables sent downwards, from content to a worker or from a worker to a
+ // child, keep the sender alive because they are WorkerThreadModifyBusyCount
+ // runnables, and so leave this null.
+ RefPtr<ThreadSafeWorkerRef> mSender;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_workerrunnable_h__