Line data Source code
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 : #include "mozilla/CycleCollectedJSContext.h"
8 : #include <algorithm>
9 : #include "mozilla/ArrayUtils.h"
10 : #include "mozilla/AutoRestore.h"
11 : #include "mozilla/CycleCollectedJSRuntime.h"
12 : #include "mozilla/Move.h"
13 : #include "mozilla/MemoryReporting.h"
14 : #include "mozilla/Sprintf.h"
15 : #include "mozilla/Telemetry.h"
16 : #include "mozilla/TimelineConsumers.h"
17 : #include "mozilla/TimelineMarker.h"
18 : #include "mozilla/Unused.h"
19 : #include "mozilla/DebuggerOnGCRunnable.h"
20 : #include "mozilla/dom/DOMJSClass.h"
21 : #include "mozilla/dom/DOMException.h"
22 : #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
23 : #include "mozilla/dom/Promise.h"
24 : #include "mozilla/dom/PromiseBinding.h"
25 : #include "mozilla/dom/PromiseDebugging.h"
26 : #include "mozilla/dom/ScriptSettings.h"
27 : #include "js/Debug.h"
28 : #include "js/GCAPI.h"
29 : #include "js/Utility.h"
30 : #include "nsContentUtils.h"
31 : #include "nsCycleCollectionNoteRootCallback.h"
32 : #include "nsCycleCollectionParticipant.h"
33 : #include "nsCycleCollector.h"
34 : #include "nsDOMJSUtils.h"
35 : #include "nsDOMMutationObserver.h"
36 : #include "nsJSUtils.h"
37 : #include "nsWrapperCache.h"
38 : #include "nsStringBuffer.h"
39 :
40 : #include "nsIPlatformInfo.h"
41 : #include "nsThread.h"
42 : #include "nsThreadUtils.h"
43 : #include "xpcpublic.h"
44 :
45 : using namespace mozilla;
46 : using namespace mozilla::dom;
47 :
48 : namespace mozilla {
49 :
50 0 : CycleCollectedJSContext::CycleCollectedJSContext()
51 : : mIsPrimaryContext(true)
52 : , mRuntime(nullptr)
53 : , mJSContext(nullptr)
54 : , mDoingStableStates(false)
55 : , mTargetedMicroTaskRecursionDepth(0)
56 : , mMicroTaskLevel(0)
57 0 : , mMicroTaskRecursionDepth(0)
58 : {
59 0 : MOZ_COUNT_CTOR(CycleCollectedJSContext);
60 :
61 : // Reinitialize PerThreadAtomCache because dom/bindings/Codegen.py compares
62 : // against zero rather than JSID_VOID to detect uninitialized jsid members.
63 0 : memset(static_cast<PerThreadAtomCache*>(this), 0, sizeof(PerThreadAtomCache));
64 :
65 0 : nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
66 12 : mOwningThread = thread.forget().downcast<nsThread>().take();
67 0 : MOZ_RELEASE_ASSERT(mOwningThread);
68 4 : }
69 :
70 0 : CycleCollectedJSContext::~CycleCollectedJSContext()
71 : {
72 0 : MOZ_COUNT_DTOR(CycleCollectedJSContext);
73 : // If the allocation failed, here we are.
74 0 : if (!mJSContext) {
75 : return;
76 : }
77 :
78 0 : JS_SetContextPrivate(mJSContext, nullptr);
79 :
80 0 : mRuntime->RemoveContext(this);
81 :
82 0 : if (mIsPrimaryContext) {
83 0 : mRuntime->Shutdown(mJSContext);
84 : }
85 :
86 : // Last chance to process any events.
87 0 : CleanupIDBTransactions(mBaseRecursionDepth);
88 0 : MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
89 :
90 0 : ProcessStableStateQueue();
91 0 : MOZ_ASSERT(mStableStateEvents.IsEmpty());
92 :
93 : // Clear mPendingException first, since it might be cycle collected.
94 0 : mPendingException = nullptr;
95 :
96 0 : MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
97 0 : MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
98 :
99 0 : mUncaughtRejections.reset();
100 0 : mConsumedRejections.reset();
101 :
102 0 : JS_DestroyContext(mJSContext);
103 0 : mJSContext = nullptr;
104 :
105 0 : if (mIsPrimaryContext) {
106 0 : nsCycleCollector_forgetJSContext();
107 : } else {
108 0 : nsCycleCollector_forgetNonPrimaryContext();
109 : }
110 :
111 0 : mozilla::dom::DestroyScriptSettings();
112 :
113 0 : mOwningThread->SetScriptObserver(nullptr);
114 0 : NS_RELEASE(mOwningThread);
115 :
116 0 : if (mIsPrimaryContext) {
117 0 : delete mRuntime;
118 : }
119 0 : mRuntime = nullptr;
120 0 : }
121 :
122 : void
123 4 : CycleCollectedJSContext::InitializeCommon()
124 : {
125 8 : mRuntime->AddContext(this);
126 :
127 4 : mOwningThread->SetScriptObserver(this);
128 : // The main thread has a base recursion depth of 0, workers of 1.
129 0 : mBaseRecursionDepth = RecursionDepth();
130 :
131 0 : NS_GetCurrentThread()->SetCanInvokeJS(true);
132 :
133 4 : JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback);
134 :
135 0 : JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this);
136 4 : JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this);
137 8 : mUncaughtRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
138 8 : mConsumedRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
139 :
140 : // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
141 0 : JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this));
142 0 : }
143 :
144 : nsresult
145 4 : CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
146 : uint32_t aMaxBytes,
147 : uint32_t aMaxNurseryBytes)
148 : {
149 0 : MOZ_ASSERT(!mJSContext);
150 :
151 0 : mozilla::dom::InitScriptSettings();
152 4 : mJSContext = JS_NewContext(aMaxBytes, aMaxNurseryBytes, aParentRuntime);
153 0 : if (!mJSContext) {
154 : return NS_ERROR_OUT_OF_MEMORY;
155 : }
156 :
157 1 : mRuntime = CreateRuntime(mJSContext);
158 :
159 1 : InitializeCommon();
160 :
161 1 : nsCycleCollector_registerJSContext(this);
162 :
163 1 : return NS_OK;
164 : }
165 :
166 : nsresult
167 0 : CycleCollectedJSContext::InitializeNonPrimary(CycleCollectedJSContext* aPrimaryContext)
168 : {
169 0 : MOZ_ASSERT(!mJSContext);
170 :
171 0 : mIsPrimaryContext = false;
172 :
173 0 : mozilla::dom::InitScriptSettings();
174 0 : mJSContext = JS_NewCooperativeContext(aPrimaryContext->mJSContext);
175 0 : if (!mJSContext) {
176 : return NS_ERROR_OUT_OF_MEMORY;
177 : }
178 :
179 0 : mRuntime = aPrimaryContext->mRuntime;
180 :
181 0 : InitializeCommon();
182 :
183 0 : nsCycleCollector_registerNonPrimaryContext(this);
184 :
185 0 : return NS_OK;
186 : }
187 :
188 : /* static */ CycleCollectedJSContext*
189 203 : CycleCollectedJSContext::GetFor(JSContext* aCx)
190 : {
191 : // Cast from void* matching JS_SetContextPrivate.
192 203 : auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
193 : // Down cast.
194 203 : return static_cast<CycleCollectedJSContext*>(atomCache);
195 : }
196 :
197 : size_t
198 0 : CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
199 : {
200 0 : return 0;
201 : }
202 :
203 : class PromiseJobRunnable final : public MicroTaskRunnable
204 : {
205 : public:
206 0 : PromiseJobRunnable(JS::HandleObject aCallback,
207 : JS::HandleObject aAllocationSite,
208 : nsIGlobalObject* aIncumbentGlobal)
209 4170 : :mCallback(
210 0 : new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
211 : {
212 4170 : }
213 :
214 0 : virtual ~PromiseJobRunnable()
215 4170 : {
216 12510 : }
217 :
218 : protected:
219 4170 : virtual void Run(AutoSlowOperation& aAso) override
220 : {
221 12510 : JSObject* callback = mCallback->CallbackPreserveColor();
222 4170 : nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
223 0 : if (global && !global->IsDying()) {
224 4170 : mCallback->Call("promise callback");
225 0 : aAso.CheckForInterrupt();
226 : }
227 0 : }
228 :
229 4170 : virtual bool Suppressed() override
230 : {
231 : nsIGlobalObject* global =
232 12510 : xpc::NativeGlobal(mCallback->CallbackPreserveColor());
233 4170 : return global && global->IsInSyncOperation();
234 : }
235 :
236 : private:
237 : RefPtr<PromiseJobCallback> mCallback;
238 : };
239 :
240 : /* static */
241 : JSObject*
242 0 : CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx)
243 : {
244 0 : nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
245 0 : if (global) {
246 0 : return global->GetGlobalJSObject();
247 : }
248 : return nullptr;
249 : }
250 :
251 : /* static */
252 : bool
253 4170 : CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
254 : JS::HandleObject aJob,
255 : JS::HandleObject aAllocationSite,
256 : JS::HandleObject aIncumbentGlobal,
257 : void* aData)
258 : {
259 4170 : CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
260 4170 : MOZ_ASSERT(aCx == self->Context());
261 0 : MOZ_ASSERT(Get() == self);
262 :
263 0 : nsIGlobalObject* global = nullptr;
264 0 : if (aIncumbentGlobal) {
265 4170 : global = xpc::NativeGlobal(aIncumbentGlobal);
266 : }
267 0 : RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
268 8340 : self->DispatchToMicroTask(runnable.forget());
269 0 : return true;
270 : }
271 :
272 : /* static */
273 : void
274 0 : CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
275 : JS::HandleObject aPromise,
276 : JS::PromiseRejectionHandlingState state,
277 : void* aData)
278 : {
279 : #ifdef DEBUG
280 3 : CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
281 : #endif // DEBUG
282 3 : MOZ_ASSERT(aCx == self->Context());
283 0 : MOZ_ASSERT(Get() == self);
284 :
285 0 : if (state == JS::PromiseRejectionHandlingState::Unhandled) {
286 0 : PromiseDebugging::AddUncaughtRejection(aPromise);
287 : } else {
288 1 : PromiseDebugging::AddConsumedRejection(aPromise);
289 : }
290 0 : }
291 :
292 : already_AddRefed<Exception>
293 0 : CycleCollectedJSContext::GetPendingException() const
294 : {
295 81 : MOZ_ASSERT(mJSContext);
296 :
297 0 : nsCOMPtr<Exception> out = mPendingException;
298 162 : return out.forget();
299 : }
300 :
301 : void
302 8486 : CycleCollectedJSContext::SetPendingException(Exception* aException)
303 : {
304 0 : MOZ_ASSERT(mJSContext);
305 8486 : mPendingException = aException;
306 0 : }
307 :
308 : std::queue<RefPtr<MicroTaskRunnable>>&
309 0 : CycleCollectedJSContext::GetMicroTaskQueue()
310 : {
311 0 : MOZ_ASSERT(mJSContext);
312 0 : return mPendingMicroTaskRunnables;
313 : }
314 :
315 : std::queue<RefPtr<MicroTaskRunnable>>&
316 0 : CycleCollectedJSContext::GetDebuggerMicroTaskQueue()
317 : {
318 0 : MOZ_ASSERT(mJSContext);
319 0 : return mDebuggerMicroTaskQueue;
320 : }
321 :
322 : void
323 0 : CycleCollectedJSContext::ProcessStableStateQueue()
324 : {
325 1938 : MOZ_ASSERT(mJSContext);
326 0 : MOZ_RELEASE_ASSERT(!mDoingStableStates);
327 1938 : mDoingStableStates = true;
328 :
329 3876 : for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
330 0 : nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget();
331 0 : event->Run();
332 : }
333 :
334 1938 : mStableStateEvents.Clear();
335 1939 : mDoingStableStates = false;
336 0 : }
337 :
338 : void
339 3179 : CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth)
340 : {
341 3179 : MOZ_ASSERT(mJSContext);
342 3179 : MOZ_RELEASE_ASSERT(!mDoingStableStates);
343 3179 : mDoingStableStates = true;
344 :
345 0 : nsTArray<PendingIDBTransactionData> localQueue = std::move(mPendingIDBTransactions);
346 :
347 0 : for (uint32_t i = 0; i < localQueue.Length(); ++i)
348 : {
349 0 : PendingIDBTransactionData& data = localQueue[i];
350 0 : if (data.mRecursionDepth != aRecursionDepth) {
351 : continue;
352 : }
353 :
354 : {
355 0 : nsCOMPtr<nsIRunnable> transaction = data.mTransaction.forget();
356 0 : transaction->Run();
357 : }
358 :
359 0 : localQueue.RemoveElementAt(i--);
360 : }
361 :
362 : // If the queue has events in it now, they were added from something we called,
363 : // so they belong at the end of the queue.
364 0 : localQueue.AppendElements(mPendingIDBTransactions);
365 3179 : localQueue.SwapElements(mPendingIDBTransactions);
366 3179 : mDoingStableStates = false;
367 0 : }
368 :
369 : void
370 1943 : CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock)
371 : {
372 : // If ProcessNextEvent was called during a microtask callback, we
373 : // must process any pending microtasks before blocking in the event loop,
374 : // otherwise we may deadlock until an event enters the queue later.
375 1943 : if (aMightBlock && PerformMicroTaskCheckPoint()) {
376 : // If any microtask was processed, we post a dummy event in order to
377 : // force the ProcessNextEvent call not to block. This is required
378 : // to support nested event loops implemented using a pattern like
379 : // "while (condition) thread.processNextEvent(true)", in case the
380 : // condition is triggered here by a Promise "then" callback.
381 0 : NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
382 : }
383 1943 : }
384 :
385 : void
386 0 : CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
387 : {
388 1939 : MOZ_ASSERT(mJSContext);
389 :
390 : // See HTML 6.1.4.2 Processing model
391 :
392 : // Step 4.1: Execute microtasks.
393 1939 : PerformMicroTaskCheckPoint();
394 :
395 : // Step 4.2 Execute any events that were waiting for a stable state.
396 1939 : ProcessStableStateQueue();
397 :
398 : // This should be a fast test so that it won't affect the next task processing.
399 1939 : IsIdleGCTaskNeeded();
400 0 : }
401 :
402 : void
403 0 : CycleCollectedJSContext::AfterProcessMicrotasks()
404 : {
405 3179 : MOZ_ASSERT(mJSContext);
406 : // Cleanup Indexed Database transactions:
407 : // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
408 3179 : CleanupIDBTransactions(RecursionDepth());
409 1 : }
410 :
411 1 : void CycleCollectedJSContext::IsIdleGCTaskNeeded()
412 : {
413 4 : class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable
414 : {
415 : public:
416 : using mozilla::IdleRunnable::IdleRunnable;
417 :
418 : public:
419 1 : NS_IMETHOD Run() override
420 : {
421 1 : CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
422 1 : if (ccrt) {
423 0 : ccrt->RunIdleTimeGCTask();
424 : }
425 0 : return NS_OK;
426 : }
427 :
428 0 : nsresult Cancel() override
429 : {
430 0 : return NS_OK;
431 : }
432 : };
433 :
434 1939 : if (Runtime()->IsIdleGCTaskNeeded()) {
435 2 : nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
436 0 : NS_IdleDispatchToCurrentThread(gc_task.forget());
437 1 : Runtime()->SetPendingIdleGCTask();
438 : }
439 1939 : }
440 :
441 : uint32_t
442 7992 : CycleCollectedJSContext::RecursionDepth()
443 : {
444 0 : return mOwningThread->RecursionDepth();
445 : }
446 :
447 : void
448 0 : CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable)
449 : {
450 0 : MOZ_ASSERT(mJSContext);
451 0 : mStableStateEvents.AppendElement(std::move(aRunnable));
452 0 : }
453 :
454 : void
455 0 : CycleCollectedJSContext::AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction)
456 : {
457 0 : MOZ_ASSERT(mJSContext);
458 :
459 0 : PendingIDBTransactionData data;
460 0 : data.mTransaction = aTransaction;
461 :
462 0 : MOZ_ASSERT(mOwningThread);
463 0 : data.mRecursionDepth = RecursionDepth();
464 :
465 : // There must be an event running to get here.
466 : #ifndef MOZ_WIDGET_COCOA
467 0 : MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
468 : #else
469 : // XXX bug 1261143
470 : // Recursion depth should be greater than mBaseRecursionDepth,
471 : // or the runnable will stay in the queue forever.
472 : if (data.mRecursionDepth <= mBaseRecursionDepth) {
473 : data.mRecursionDepth = mBaseRecursionDepth + 1;
474 : }
475 : #endif
476 :
477 0 : mPendingIDBTransactions.AppendElement(std::move(data));
478 0 : }
479 :
480 : void
481 0 : CycleCollectedJSContext::DispatchToMicroTask(
482 : already_AddRefed<MicroTaskRunnable> aRunnable)
483 : {
484 0 : RefPtr<MicroTaskRunnable> runnable(aRunnable);
485 :
486 4177 : MOZ_ASSERT(NS_IsMainThread());
487 4177 : MOZ_ASSERT(runnable);
488 :
489 0 : mPendingMicroTaskRunnables.push(runnable.forget());
490 4177 : }
491 :
492 0 : class AsyncMutationHandler final : public mozilla::Runnable
493 : {
494 : public:
495 0 : AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
496 :
497 0 : NS_IMETHOD Run() override
498 : {
499 0 : CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
500 0 : if (ccjs) {
501 0 : ccjs->PerformMicroTaskCheckPoint();
502 : }
503 0 : return NS_OK;
504 : }
505 : };
506 :
507 : bool
508 0 : CycleCollectedJSContext::PerformMicroTaskCheckPoint()
509 : {
510 17048 : if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
511 0 : AfterProcessMicrotasks();
512 : // Nothing to do, return early.
513 2898 : return false;
514 : }
515 :
516 0 : uint32_t currentDepth = RecursionDepth();
517 0 : if (mMicroTaskRecursionDepth >= currentDepth) {
518 : // We are already executing microtasks for the current recursion depth.
519 : return false;
520 : }
521 :
522 0 : if (mTargetedMicroTaskRecursionDepth != 0 &&
523 : mTargetedMicroTaskRecursionDepth != currentDepth) {
524 : return false;
525 : }
526 :
527 0 : if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
528 : // Special case for main thread where DOM mutations may happen when
529 : // it is not safe to run scripts.
530 0 : nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
531 0 : return false;
532 : }
533 :
534 843 : mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
535 0 : MOZ_ASSERT(currentDepth > 0);
536 281 : mMicroTaskRecursionDepth = currentDepth;
537 :
538 281 : bool didProcess = false;
539 0 : AutoSlowOperation aso;
540 :
541 843 : std::queue<RefPtr<MicroTaskRunnable>> suppressed;
542 : for (;;) {
543 0 : RefPtr<MicroTaskRunnable> runnable;
544 8916 : if (!mDebuggerMicroTaskQueue.empty()) {
545 0 : runnable = mDebuggerMicroTaskQueue.front().forget();
546 0 : mDebuggerMicroTaskQueue.pop();
547 8916 : } else if (!mPendingMicroTaskRunnables.empty()) {
548 12531 : runnable = mPendingMicroTaskRunnables.front().forget();
549 4177 : mPendingMicroTaskRunnables.pop();
550 : } else {
551 : break;
552 : }
553 :
554 4177 : if (runnable->Suppressed()) {
555 : // Microtasks in worker shall never be suppressed.
556 : // Otherwise, mPendingMicroTaskRunnables will be replaced later with
557 : // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
558 0 : MOZ_ASSERT(NS_IsMainThread());
559 : suppressed.push(runnable);
560 : } else {
561 4177 : didProcess = true;
562 4177 : runnable->Run(aso);
563 : }
564 : }
565 :
566 : // Put back the suppressed microtasks so that they will be run later.
567 : // Note, it is possible that we end up keeping these suppressed tasks around
568 : // for some time, but no longer than spinning the event loop nestedly
569 : // (sync XHR, alert, etc.)
570 562 : mPendingMicroTaskRunnables.swap(suppressed);
571 :
572 281 : AfterProcessMicrotasks();
573 :
574 281 : return didProcess;
575 : }
576 :
577 : void
578 0 : CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
579 : {
580 : // Don't do normal microtask handling checks here, since whoever is calling
581 : // this method is supposed to know what they are doing.
582 :
583 0 : AutoSlowOperation aso;
584 : for (;;) {
585 : // For a debugger microtask checkpoint, we always use the debugger microtask
586 : // queue.
587 : std::queue<RefPtr<MicroTaskRunnable>>* microtaskQueue =
588 : &GetDebuggerMicroTaskQueue();
589 :
590 : if (microtaskQueue->empty()) {
591 : break;
592 : }
593 :
594 : RefPtr<MicroTaskRunnable> runnable = microtaskQueue->front().forget();
595 : MOZ_ASSERT(runnable);
596 :
597 : // This function can re-enter, so we remove the element before calling.
598 : microtaskQueue->pop();
599 : runnable->Run(aso);
600 : }
601 :
602 : AfterProcessMicrotasks();
603 : }
604 : } // namespace mozilla
|