summaryrefslogtreecommitdiffstats
path: root/dom/promise/PromiseWorkerProxy.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/promise/PromiseWorkerProxy.h')
-rw-r--r--dom/promise/PromiseWorkerProxy.h226
1 files changed, 226 insertions, 0 deletions
diff --git a/dom/promise/PromiseWorkerProxy.h b/dom/promise/PromiseWorkerProxy.h
new file mode 100644
index 000000000..bcb44d38f
--- /dev/null
+++ b/dom/promise/PromiseWorkerProxy.h
@@ -0,0 +1,226 @@
+/* -*- 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_PromiseWorkerProxy_h
+#define mozilla_dom_PromiseWorkerProxy_h
+
+// Required for Promise::PromiseTaskSync.
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+#include "nsProxyRelease.h"
+
+#include "WorkerRunnable.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+// A proxy to (eventually) mirror a resolved/rejected Promise's result from the
+// main thread to a Promise on the worker thread.
+//
+// How to use:
+//
+// 1. Create a Promise on the worker thread and return it to the content
+// script:
+//
+// RefPtr<Promise> promise = Promise::Create(workerPrivate->GlobalScope(), aRv);
+// if (aRv.Failed()) {
+// return nullptr;
+// }
+//
+// 2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the
+// worker is shutting down and you should fail the original call. This is
+// only likely to happen in (Gecko-specific) worker onclose handlers.
+//
+// RefPtr<PromiseWorkerProxy> proxy =
+// PromiseWorkerProxy::Create(workerPrivate, promise);
+// if (!proxy) {
+// // You may also reject the Promise with an AbortError or similar.
+// return nullptr;
+// }
+//
+// 3. Dispatch a runnable to the main thread, with a reference to the proxy to
+// perform the main thread operation. PromiseWorkerProxy is thread-safe
+// refcounted.
+//
+// 4. Return the worker thread promise to the JS caller:
+//
+// return promise.forget();
+//
+// 5. In your main thread runnable Run(), obtain a Promise on
+// the main thread and call its AppendNativeHandler(PromiseNativeHandler*)
+// to bind the PromiseWorkerProxy created at #2.
+//
+// 4. Then the Promise results returned by ResolvedCallback/RejectedCallback
+// will be dispatched as a WorkerRunnable to the worker thread to
+// resolve/reject the Promise created at #1.
+//
+// PromiseWorkerProxy can also be used in situations where there is no main
+// thread Promise, or where special handling is required on the worker thread
+// for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3
+// above. When the main thread is ready to resolve the worker thread promise:
+//
+// 1. Acquire the mutex before attempting to access the worker private.
+//
+// AssertIsOnMainThread();
+// MutexAutoLock lock(proxy->Lock());
+// if (proxy->CleanedUp()) {
+// // Worker has already shut down, can't access worker private.
+// return;
+// }
+//
+// 2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
+// worker.
+//
+// RefPtr<FinishTaskWorkerRunnable> runnable =
+// new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy, result);
+// if (!r->Dispatch()) {
+// // Worker is alive but not Running any more, so the Promise can't
+// // be resolved, give up. The proxy will get Release()d at some
+// // point.
+//
+// // Usually do nothing, but you may want to log the fact.
+// }
+//
+// 3. In the WorkerRunnable's WorkerRun() use WorkerPromise() to access the
+// Promise and resolve/reject it. Then call CleanUp().
+//
+// bool
+// WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+// {
+// aWorkerPrivate->AssertIsOnWorkerThread();
+// RefPtr<Promise> promise = mProxy->WorkerPromise();
+// promise->MaybeResolve(mResult);
+// mProxy->CleanUp();
+// }
+//
+// Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this
+// can happen if the main thread Promise is never fulfilled - it will
+// stay alive till the worker reaches a Canceling state, even if all external
+// references to it are dropped.
+
+class PromiseWorkerProxy : public PromiseNativeHandler
+ , public StructuredCloneHolderBase
+{
+ friend class PromiseWorkerProxyRunnable;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+public:
+ typedef JSObject* (*ReadCallbackOp)(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ const PromiseWorkerProxy* aProxy,
+ uint32_t aTag,
+ uint32_t aData);
+ typedef bool (*WriteCallbackOp)(JSContext* aCx,
+ JSStructuredCloneWriter* aWorker,
+ PromiseWorkerProxy* aProxy,
+ JS::HandleObject aObj);
+
+ struct PromiseWorkerProxyStructuredCloneCallbacks
+ {
+ ReadCallbackOp Read;
+ WriteCallbackOp Write;
+ };
+
+ static already_AddRefed<PromiseWorkerProxy>
+ Create(workers::WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
+
+ // Main thread callers must hold Lock() and check CleanUp() before calling this.
+ // Worker thread callers, this will assert that the proxy has not been cleaned
+ // up.
+ workers::WorkerPrivate* GetWorkerPrivate() const;
+
+ // This should only be used within WorkerRunnable::WorkerRun() running on the
+ // worker thread! Do not call this after calling CleanUp().
+ Promise* WorkerPromise() const;
+
+ // Worker thread only. Calling this invalidates several assumptions, so be
+ // sure this is the last thing you do.
+ // 1. WorkerPrivate() will no longer return a valid worker.
+ // 2. WorkerPromise() will crash!
+ void CleanUp();
+
+ Mutex& Lock()
+ {
+ return mCleanUpLock;
+ }
+
+ bool CleanedUp() const
+ {
+ mCleanUpLock.AssertCurrentThreadOwns();
+ return mCleanedUp;
+ }
+
+ // StructuredCloneHolderBase
+
+ JSObject* CustomReadHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ uint32_t aIndex) override;
+
+ bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj) override;
+
+protected:
+ virtual void ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+ virtual void RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) override;
+
+private:
+ PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
+
+ virtual ~PromiseWorkerProxy();
+
+ bool AddRefObject();
+
+ // If not called from Create(), be sure to hold Lock().
+ void CleanProperties();
+
+ // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
+ typedef void (Promise::*RunCallbackFunc)(JSContext*,
+ JS::Handle<JS::Value>);
+
+ void RunCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ RunCallbackFunc aFunc);
+
+ // Any thread with appropriate checks.
+ workers::WorkerPrivate* mWorkerPrivate;
+
+ // Worker thread only.
+ RefPtr<Promise> mWorkerPromise;
+
+ // Modified on the worker thread.
+ // It is ok to *read* this without a lock on the worker.
+ // Main thread must always acquire a lock.
+ bool mCleanedUp; // To specify if the cleanUp() has been done.
+
+ const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks;
+
+ // Ensure the worker and the main thread won't race to access |mCleanedUp|.
+ Mutex mCleanUpLock;
+
+ UniquePtr<workers::WorkerHolder> mWorkerHolder;
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseWorkerProxy_h