tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PromiseWorkerProxy.h (8324B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef mozilla_dom_PromiseWorkerProxy_h
      8 #define mozilla_dom_PromiseWorkerProxy_h
      9 
     10 #include <cstdint>
     11 
     12 #include "js/TypeDecls.h"
     13 #include "mozilla/AlreadyAddRefed.h"
     14 #include "mozilla/Mutex.h"
     15 #include "mozilla/RefPtr.h"
     16 #include "mozilla/dom/Promise.h"
     17 #include "mozilla/dom/PromiseNativeHandler.h"
     18 #include "mozilla/dom/StructuredCloneHolder.h"
     19 #include "nsISupports.h"
     20 
     21 struct JSStructuredCloneReader;
     22 struct JSStructuredCloneWriter;
     23 
     24 namespace JS {
     25 class CloneDataPolicy;
     26 }  // namespace JS
     27 
     28 namespace mozilla::dom {
     29 
     30 class ThreadSafeWorkerRef;
     31 class WorkerPrivate;
     32 
     33 // A proxy to (eventually) mirror a resolved/rejected Promise's result from the
     34 // main thread to a Promise on the worker thread.
     35 //
     36 // How to use:
     37 //
     38 //   1. Create a Promise on the worker thread and return it to the content
     39 //      script:
     40 //
     41 //        RefPtr<Promise> promise =
     42 //          Promise::Create(workerPrivate->GlobalScope(), aRv);
     43 //        if (aRv.Failed()) {
     44 //          return nullptr;
     45 //        }
     46 //
     47 //   2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the
     48 //      worker is shutting down and you should fail the original call. This is
     49 //      only likely to happen in (Gecko-specific) worker onclose handlers.
     50 //
     51 //        RefPtr<PromiseWorkerProxy> proxy =
     52 //          PromiseWorkerProxy::Create(workerPrivate, promise);
     53 //        if (!proxy) {
     54 //          // You may also reject the Promise with an AbortError or similar.
     55 //          return nullptr;
     56 //        }
     57 //
     58 //   3. Dispatch a runnable to the main thread, with a reference to the proxy to
     59 //      perform the main thread operation. PromiseWorkerProxy is thread-safe
     60 //      refcounted.
     61 //
     62 //   4. Return the worker thread promise to the JS caller:
     63 //
     64 //        return promise.forget();
     65 //
     66 //   5. In your main thread runnable Run(), obtain a Promise on
     67 //      the main thread and call its AppendNativeHandler(PromiseNativeHandler*)
     68 //      to bind the PromiseWorkerProxy created at #2.
     69 //
     70 //   4. Then the Promise results returned by ResolvedCallback/RejectedCallback
     71 //      will be dispatched as a WorkerRunnable to the worker thread to
     72 //      resolve/reject the Promise created at #1.
     73 //
     74 // PromiseWorkerProxy can also be used in situations where there is no main
     75 // thread Promise, or where special handling is required on the worker thread
     76 // for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3
     77 // above. When the main thread is ready to resolve the worker thread promise:
     78 //
     79 //   1. Acquire the mutex before attempting to access the worker private.
     80 //
     81 //        AssertIsOnMainThread();
     82 //        MutexAutoLock lock(proxy->Lock());
     83 //        if (proxy->CleanedUp()) {
     84 //          // Worker has already shut down, can't access worker private.
     85 //          return;
     86 //        }
     87 //
     88 //   2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
     89 //      worker.
     90 //
     91 //        RefPtr<FinishTaskWorkerRunnable> runnable =
     92 //          new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy,
     93 //                                       result);
     94 //        if (!r->Dispatch()) {
     95 //          // Worker is alive but not Running any more, so the Promise can't
     96 //          // be resolved, give up. The proxy will get Release()d at some
     97 //          // point.
     98 //
     99 //          // Usually do nothing, but you may want to log the fact.
    100 //        }
    101 //
    102 //   3. In the WorkerRunnable's WorkerRun() use GetWorkerPromise() to access the
    103 //      Promise and resolve/reject it. Then call CleanUp().
    104 //
    105 //        bool
    106 //        WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
    107 //        {
    108 //          aWorkerPrivate->AssertIsOnWorkerThread();
    109 //          RefPtr<Promise> promise = mProxy->GetWorkerPromise();
    110 //          promise->MaybeResolve(mResult);
    111 //          mProxy->CleanUp();
    112 //        }
    113 //
    114 // Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this
    115 // can happen if the main thread Promise is never fulfilled - it will
    116 // stay alive till the worker reaches a Canceling state, even if all external
    117 // references to it are dropped.
    118 
    119 class PromiseWorkerProxy : public PromiseNativeHandler,
    120                           public StructuredCloneHolderBase {
    121  friend class PromiseWorkerProxyRunnable;
    122 
    123  NS_DECL_THREADSAFE_ISUPPORTS
    124 
    125 public:
    126  typedef JSObject* (*ReadCallbackOp)(JSContext* aCx,
    127                                      JSStructuredCloneReader* aReader,
    128                                      const PromiseWorkerProxy* aProxy,
    129                                      uint32_t aTag, uint32_t aData);
    130  typedef bool (*WriteCallbackOp)(JSContext* aCx,
    131                                  JSStructuredCloneWriter* aWorker,
    132                                  PromiseWorkerProxy* aProxy,
    133                                  JS::Handle<JSObject*> aObj);
    134 
    135  struct PromiseWorkerProxyStructuredCloneCallbacks {
    136    ReadCallbackOp Read;
    137    WriteCallbackOp Write;
    138  };
    139 
    140  static already_AddRefed<PromiseWorkerProxy> Create(
    141      WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
    142      const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
    143 
    144  // Main thread callers must hold Lock() and check CleanUp() before calling
    145  // this. Worker thread callers, this will assert that the proxy has not been
    146  // cleaned up.
    147  WorkerPrivate* GetWorkerPrivate() const MOZ_NO_THREAD_SAFETY_ANALYSIS;
    148 
    149  // This should only be used within WorkerRunnable::WorkerRun() running on the
    150  // worker thread! If this method is called after CleanUp(), return nullptr.
    151  Promise* GetWorkerPromise() const;
    152 
    153  // Worker thread only. Calling this invalidates several assumptions, so be
    154  // sure this is the last thing you do.
    155  // 1. WorkerPrivate() will no longer return a valid worker.
    156  // 2. GetWorkerPromise() will return null!
    157  void CleanUp();
    158 
    159  Mutex& Lock() MOZ_RETURN_CAPABILITY(mCleanUpLock) { return mCleanUpLock; }
    160 
    161  bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) {
    162    mCleanUpLock.AssertCurrentThreadOwns();
    163    return mCleanedUp;
    164  }
    165 
    166  // StructuredCloneHolderBase
    167 
    168  JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
    169                              const JS::CloneDataPolicy& aCloneDataPolicy,
    170                              uint32_t aTag, uint32_t aIndex) override;
    171 
    172  bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
    173                          JS::Handle<JSObject*> aObj,
    174                          bool* aSameProcessScopeRequired) override;
    175 
    176 protected:
    177  virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    178                                ErrorResult& aRv) override;
    179 
    180  virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    181                                ErrorResult& aRv) override;
    182 
    183 private:
    184  explicit PromiseWorkerProxy(
    185      Promise* aWorkerPromise,
    186      const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr);
    187 
    188  virtual ~PromiseWorkerProxy();
    189 
    190  // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
    191  typedef void (Promise::*RunCallbackFunc)(JSContext*, JS::Handle<JS::Value>);
    192 
    193  void RunCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
    194                   RunCallbackFunc aFunc);
    195 
    196  // Any thread with appropriate checks.
    197  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
    198 
    199  // Worker thread only.
    200  RefPtr<Promise> mWorkerPromise;
    201 
    202  // Modified on the worker thread.
    203  // It is ok to *read* this without a lock on the worker.
    204  // Main thread must always acquire a lock.
    205  bool mCleanedUp MOZ_GUARDED_BY(
    206      mCleanUpLock);  // To specify if the cleanUp() has been done.
    207 
    208  const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks;
    209 
    210  // Ensure the worker and the main thread won't race to access |mCleanedUp|.
    211  // This could perhaps be a type similar to `EventTargetAndLockCapability`
    212  // guarding `mCleanedUp` and `mWorkerRef`, however doing so naively could
    213  // inflate the size of this object.
    214  Mutex mCleanUpLock;
    215 };
    216 }  // namespace mozilla::dom
    217 
    218 #endif  // mozilla_dom_PromiseWorkerProxy_h