@@ -49,15 +49,29 @@ struct JobQueueTraps {
4949 JS::HandleObject allocationSite,
5050 JS::HandleObject incumbentGlobal) = 0 ;
5151 bool (*empty)(const void * queue);
52+
53+ // Create a new queue, push it onto an embedder-side stack, and return the new
54+ // queue.
55+ const void * (*pushNewInterruptQueue)(void * aInterruptQueues);
56+ // Destroy the queue most recently created by pushNewInterruptQueue(),
57+ // returning its address so we can check if we are restoring the saved queue
58+ // over the correct queue.
59+ const void * (*popInterruptQueue)(void * aInterruptQueues);
60+ // Destroy the embedder-side stack of interrupt queues.
61+ void (*dropInterruptQueues)(void * aInterruptQueues);
5262};
5363
5464class RustJobQueue : public JS ::JobQueue {
5565 JobQueueTraps mTraps ;
5666 const void * mQueue ;
67+ void * mInterruptQueues ;
5768
5869 public:
59- RustJobQueue (const JobQueueTraps& aTraps, const void * aQueue)
60- : mTraps (aTraps), mQueue (aQueue) {}
70+ RustJobQueue (const JobQueueTraps& aTraps, const void * aQueue,
71+ void * aInterruptQueues)
72+ : mTraps (aTraps), mQueue (aQueue), mInterruptQueues (aInterruptQueues) {}
73+
74+ ~RustJobQueue () { mTraps .dropInterruptQueues (mInterruptQueues ); }
6175
6276 virtual JSObject* getIncumbentGlobal (JSContext* cx) override {
6377 return mTraps .getIncumbentGlobal (mQueue , cx);
@@ -80,9 +94,83 @@ class RustJobQueue : public JS::JobQueue {
8094 bool isDrainingStopped () const override { return false ; }
8195
8296 private:
97+ class SavedQueue : public JS ::JobQueue::SavedJobQueue {
98+ public:
99+ SavedQueue (const JobQueueTraps& aTraps, void * aInterruptQueues,
100+ const void ** aCurrentQueue, const void * aNewQueue)
101+ : mTraps (aTraps),
102+ mInterruptQueues (aInterruptQueues),
103+ mCurrentQueue(aCurrentQueue),
104+ mNewQueue(aNewQueue),
105+ mSavedQueue(*aCurrentQueue) {
106+ // TODO: assert that the context’s jobQueue hasn’t been cleared with
107+ // SetJobQueue(nullptr) or DestroyContext(). Don’t know how to do this
108+ // with only an opaque JSContext decl. Are we allowed to #include
109+ // "vm/JSContext.h"?
110+ //
111+ // MOZ_ASSERT(cx->jobQueue.ref());
112+
113+ // Set the current queue to mNewQueue.
114+ // We need to take care of this, so that we can save the old queue in the
115+ // member initializers above.
116+ *mCurrentQueue = mNewQueue ;
117+ }
118+
119+ ~SavedQueue () {
120+ // TODO: assert that the context’s jobQueue hasn’t been cleared with
121+ // SetJobQueue(nullptr) or DestroyContext(). Don’t know how to do this
122+ // with only an opaque JSContext decl. Are we allowed to #include
123+ // "vm/JSContext.h"?
124+ //
125+ // MOZ_ASSERT(cx->jobQueue.ref());
126+
127+ // Check that the current queue is empty, as required by the SavedJobQueue
128+ // contract.
129+ MOZ_ASSERT (mTraps .empty (*mCurrentQueue ));
130+
131+ // Destroy the topmost queue, checking that it was the queue this
132+ // SavedQueue expects to restore from. Imagine we have normal queue A,
133+ // then we switch to B (SavedQueue from B to A), then we switch to C
134+ // (SavedQueue from C to B). If the SavedQueue from B to A is restored
135+ // before the SavedQueue from C to B, the embedder will destroy both C and
136+ // B, but in the end, the queue will be set to B, a freed queue.
137+ MOZ_ASSERT (mTraps .popInterruptQueue (mInterruptQueues ) == mNewQueue );
138+
139+ *mCurrentQueue = mSavedQueue ;
140+ }
141+
142+ private:
143+ // Required for embedder FFI.
144+ JobQueueTraps mTraps ;
145+ void * mInterruptQueues ;
146+
147+ // Pointer to the RustJobQueue::mQueue field to write to when switching.
148+ const void ** mCurrentQueue ;
149+
150+ // The queue to switch to when saving.
151+ const void * mNewQueue ;
152+
153+ // The queue to switch to when restoring.
154+ const void * mSavedQueue ;
155+ };
156+
83157 virtual js::UniquePtr<SavedJobQueue> saveJobQueue (JSContext* cx) override {
84- MOZ_ASSERT (false , " saveJobQueue should not be invoked" );
85- return nullptr ;
158+ auto newQueue = mTraps .pushNewInterruptQueue (mInterruptQueues );
159+ // Servo uses infallible allocation here, so it should never return nullptr.
160+ MOZ_ASSERT (!!newQueue);
161+
162+ auto result =
163+ js::MakeUnique<SavedQueue>(mTraps , mInterruptQueues , &mQueue , newQueue);
164+ if (!result) {
165+ // “On OOM, this should call JS_ReportOutOfMemory on the given JSContext,
166+ // and return a null UniquePtr.”
167+ //
168+ // When the allocation in MakeUnique() fails, the SavedQueue constructor
169+ // is never called, so this->mQueue is still set to the old queue.
170+ js::ReportOutOfMemory (cx);
171+ return nullptr ;
172+ }
173+ return result;
86174 }
87175};
88176
@@ -1012,8 +1100,9 @@ JSString* JS_ForgetStringLinearness(JSLinearString* str) {
10121100 return JS_FORGET_STRING_LINEARNESS (str);
10131101}
10141102
1015- JS::JobQueue* CreateJobQueue (const JobQueueTraps* aTraps, const void * aQueue) {
1016- return new RustJobQueue (*aTraps, aQueue);
1103+ JS::JobQueue* CreateJobQueue (const JobQueueTraps* aTraps, const void * aQueue,
1104+ void * aInterruptQueues) {
1105+ return new RustJobQueue (*aTraps, aQueue, aInterruptQueues);
10171106}
10181107
10191108void DeleteJobQueue (JS::JobQueue* queue) { delete queue; }
0 commit comments