GetWebTaskSchedulingState() is called for every promise job that needs host defined data, and each call walks the script settings stack for the entry global and does a principal check, even though almost nothing uses scheduler.postTask(): Have CycleCollectedJSContext count how many of its globals currently have a WebTaskSchedulingState, so that getHostDefinedData - the only hot caller - can answer the common case with a member load instead. Differential Revision: https://phabricator.services.mozilla.com/D320168
689 lines
23 KiB
C++
689 lines
23 KiB
C++
/* 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_CycleCollectedJSContext_h
|
|
#define mozilla_CycleCollectedJSContext_h
|
|
|
|
#include "js/GCVector.h"
|
|
#include "js/Promise.h"
|
|
#include "js/TracingAPI.h"
|
|
#include "js/friend/MicroTask.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/dom/AtomList.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsRefPtrHashtable.h"
|
|
#include "nsTArray.h"
|
|
#include "nsTHashMap.h"
|
|
|
|
class nsCycleCollectionNoteRootCallback;
|
|
class nsIRunnable;
|
|
class nsThread;
|
|
|
|
namespace mozilla {
|
|
class AutoSlowOperation;
|
|
|
|
class CycleCollectedJSContext;
|
|
class CycleCollectedJSRuntime;
|
|
|
|
namespace dom {
|
|
class Exception;
|
|
class WorkerJSContext;
|
|
class WorkletJSContext;
|
|
} // namespace dom
|
|
|
|
// Contains various stats about the cycle collection.
|
|
struct CycleCollectorResults {
|
|
CycleCollectorResults() {
|
|
// Initialize here so when we increment mNumSlices the first time we're
|
|
// not using uninitialized memory.
|
|
Init();
|
|
}
|
|
|
|
void Init() {
|
|
mForcedGC = false;
|
|
mSuspectedAtCCStart = 0;
|
|
mMergedZones = false;
|
|
mAnyManual = false;
|
|
mVisitedRefCounted = 0;
|
|
mVisitedGCed = 0;
|
|
mFreedRefCounted = 0;
|
|
mFreedGCed = 0;
|
|
mFreedJSZones = 0;
|
|
mNumSlices = 1;
|
|
// mNumSlices is initialized to one, because we call Init() after the
|
|
// per-slice increment of mNumSlices has already occurred.
|
|
}
|
|
|
|
bool mForcedGC;
|
|
bool mMergedZones;
|
|
// mAnyManual is true if any slice was manually triggered, and at shutdown.
|
|
bool mAnyManual;
|
|
uint32_t mSuspectedAtCCStart;
|
|
uint32_t mVisitedRefCounted;
|
|
uint32_t mVisitedGCed;
|
|
uint32_t mFreedRefCounted;
|
|
uint32_t mFreedGCed;
|
|
uint32_t mFreedJSZones;
|
|
uint32_t mNumSlices;
|
|
};
|
|
|
|
class MicroTaskRunnable : public LinkedListElement<MicroTaskRunnable> {
|
|
public:
|
|
MicroTaskRunnable() = default;
|
|
NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable)
|
|
MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0;
|
|
virtual bool Suppressed() { return false; }
|
|
virtual void TraceMicroTask(JSTracer* aTracer) {}
|
|
|
|
protected:
|
|
virtual ~MicroTaskRunnable() {
|
|
if (isInList()) {
|
|
remove();
|
|
}
|
|
}
|
|
};
|
|
|
|
// A wrapper for JS::MicroTaskType. Defines the interface for data access.
|
|
// Base class that holds the microtask data and provides information-gathering
|
|
// methods. Can't be instantiated directly.
|
|
class MOZ_STACK_CLASS MayConsumeMicroTask {
|
|
public:
|
|
virtual ~MayConsumeMicroTask() = default;
|
|
|
|
// Check if this holds a "JS Microtask" (see MicroTask.h),
|
|
// which is a task enqueued by the JS engine rather than
|
|
// Gecko.
|
|
bool IsJSMicroTask() const { return JS::IsJSMicroTask(mMicroTask); }
|
|
|
|
// Unwrap (without interacting with refcounting) a Gecko MicroTaskRunnable if
|
|
// the task is not a JS MicroTask (see MicroTask.h for "JS MicroTask");
|
|
// otherwise, return nullptr.
|
|
//
|
|
// This is a non-owning conversion: This class still owns the refcount.
|
|
MicroTaskRunnable* MaybeUnwrapTaskToRunnable() const;
|
|
|
|
// Get the execution global for this task without
|
|
// consuming the contents.
|
|
JSObject* GetExecutionGlobalFromJSMicroTask() const {
|
|
MOZ_ASSERT(IsJSMicroTask());
|
|
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
|
|
MOZ_ASSERT(task);
|
|
return JS::GetExecutionGlobalFromJSMicroTask(task);
|
|
}
|
|
|
|
// Below: A number of wrappers to allow working with a MicroTask without
|
|
// exposing the contained task which could then be misused.
|
|
//
|
|
// These are documented in MicroTask.h.
|
|
|
|
bool GetFlowIdFromJSMicroTask(uint64_t* aFlowId) const {
|
|
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
|
|
MOZ_ASSERT(task);
|
|
return JS::GetFlowIdFromJSMicroTask(task, aFlowId);
|
|
}
|
|
|
|
JSObject* MaybeGetPromiseFromJSMicroTask() const {
|
|
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
|
|
MOZ_ASSERT(task);
|
|
return JS::MaybeGetPromiseFromJSMicroTask(task);
|
|
}
|
|
|
|
bool MaybeGetHostDefinedDataFromJSMicroTask(
|
|
JS::MutableHandle<JSObject*> aIncumbentGlobal,
|
|
JS::MutableHandle<JSObject*> aOptionalHostDefinedData) const {
|
|
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
return JS::MaybeGetHostDefinedDataFromJSMicroTask(task, aIncumbentGlobal,
|
|
aOptionalHostDefinedData);
|
|
}
|
|
|
|
bool MaybeGetAllocationSiteFromJSMicroTask(
|
|
JS::MutableHandle<JSObject*> out) const {
|
|
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
|
|
if (!task) {
|
|
return false;
|
|
}
|
|
return JS::MaybeGetAllocationSiteFromJSMicroTask(task, out);
|
|
}
|
|
|
|
void trace(JSTracer* aTrc) {
|
|
TraceRoot(aTrc, &mMicroTask, "MayConsumeMicroTask value");
|
|
}
|
|
|
|
protected:
|
|
explicit MayConsumeMicroTask(JS::GenericMicroTask aMicroTask)
|
|
: mMicroTask(aMicroTask) {}
|
|
|
|
JS::GenericMicroTask mMicroTask;
|
|
};
|
|
|
|
// A gecko wrapper for the JS::MicroTask type. Used to enforce both
|
|
// that this is handled move only, but also that we have succesfully
|
|
// consumed this microtask before destruction.
|
|
//
|
|
// This type must be rooted, it holds onto a JS reference.
|
|
class MOZ_STACK_CLASS MustConsumeMicroTask : public MayConsumeMicroTask {
|
|
public:
|
|
// We need a public constructor to allow forward declared Rooted
|
|
MustConsumeMicroTask() : MayConsumeMicroTask(JS::GenericMicroTask()) {}
|
|
|
|
// The only way to get a (filled) MustConsumeMicroTask is through these
|
|
// mechanisms.
|
|
friend MustConsumeMicroTask DequeueNextMicroTask(JSContext* aCx);
|
|
friend MustConsumeMicroTask DequeueNextRegularMicroTask(JSContext* aCx);
|
|
friend MustConsumeMicroTask DequeueNextDebuggerMicroTask(JSContext* aCx);
|
|
|
|
~MustConsumeMicroTask() override {
|
|
if (!mMicroTask.isUndefined()) {
|
|
MOZ_CRASH("Didn't consume MicroTask");
|
|
}
|
|
}
|
|
|
|
// Move only semantics
|
|
MustConsumeMicroTask(const MustConsumeMicroTask&) = delete;
|
|
MustConsumeMicroTask& operator=(const MustConsumeMicroTask&) = delete;
|
|
MustConsumeMicroTask(MustConsumeMicroTask&& other)
|
|
: MayConsumeMicroTask(other.mMicroTask) {
|
|
other.mMicroTask.setUndefined();
|
|
}
|
|
MustConsumeMicroTask& operator=(MustConsumeMicroTask&& other) noexcept {
|
|
if (this != &other) {
|
|
mMicroTask = other.mMicroTask;
|
|
other.mMicroTask.setUndefined();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
// Indicate if this still holds a task or not.
|
|
bool IsConsumed() const { return mMicroTask.isUndefined(); }
|
|
|
|
// Allow testing for contentfulness.
|
|
explicit operator bool() const { return !IsConsumed(); }
|
|
|
|
// Take ownership of a non-JS task inside a JS::GenericMicroTask - This clears
|
|
// the contents of the value to make it clear that we've transfered ownership.
|
|
// `this` is marked is only edited if unwrapping succeeds, and so
|
|
// you can conditionally try to consume as owned;
|
|
//
|
|
// MOZ_ASSERT(!mustConsume.IsConsumed())
|
|
// if (RefPtr<MicroTaskRunnable> geckoTask =
|
|
// mustConsume.MaybeConsumeAsOwnedRunnable()) {
|
|
// // mustConsume is now empty
|
|
// } else {
|
|
// // mustConsume still holds a JS microtask
|
|
// }
|
|
//
|
|
already_AddRefed<MicroTaskRunnable> MaybeConsumeAsOwnedRunnable();
|
|
|
|
// Intentionally ignore a JS microtask. This can happen when script
|
|
// execution is disallowed during CallSetup
|
|
void IgnoreJSMicroTask() {
|
|
MOZ_ASSERT(IsJSMicroTask());
|
|
mMicroTask.setUndefined();
|
|
}
|
|
|
|
// Consume this by prepending this MustConsumeMicroTask back into
|
|
// the MicroTaskQueue.
|
|
void ConsumeByPrependToQueue(JSContext* aCx) {
|
|
MOZ_ASSERT(!IsConsumed(), "Attempting to consume an already-consumed task");
|
|
if (!JS::PrependMicroTask(aCx, mMicroTask)) {
|
|
// Can't lose tasks.
|
|
NS_ABORT_OOM(0);
|
|
}
|
|
mMicroTask.setUndefined();
|
|
}
|
|
|
|
bool RunAndConsumeJSMicroTask(JSContext* aCx) {
|
|
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
|
|
JS::Rooted<JS::JSMicroTask*> task(
|
|
aCx, JS::ToMaybeWrappedJSMicroTask(mMicroTask));
|
|
MOZ_ASSERT(task);
|
|
bool v = JS::RunJSMicroTask(aCx, task);
|
|
mMicroTask.setUndefined();
|
|
return v;
|
|
}
|
|
|
|
private:
|
|
explicit MustConsumeMicroTask(JS::GenericMicroTask aMicroTask)
|
|
: MayConsumeMicroTask(aMicroTask) {}
|
|
};
|
|
|
|
// To allow using the same accessors for data as MustConsumeMicroTask
|
|
// we create this subclass which will never consume the microtask
|
|
// for the purposes of peeking.
|
|
//
|
|
// This type must be rooted, it holds onto a JS reference.
|
|
class MOZ_STACK_CLASS WontConsumeMicroTask : public MayConsumeMicroTask {
|
|
public:
|
|
// // We need a public constructor to allow forward declared Rooted
|
|
WontConsumeMicroTask() : MayConsumeMicroTask(JS::GenericMicroTask()) {}
|
|
|
|
// The only way to get a (filled) WontConsumeMicroTask is through peeking.
|
|
friend WontConsumeMicroTask PeekNextMicroTask(JSContext* aCx);
|
|
|
|
~WontConsumeMicroTask() = default;
|
|
|
|
private:
|
|
explicit WontConsumeMicroTask(JS::GenericMicroTask aMicroTask)
|
|
: MayConsumeMicroTask(aMicroTask) {
|
|
MOZ_RELEASE_ASSERT(!aMicroTask.isNullOrUndefined());
|
|
}
|
|
};
|
|
|
|
class SuppressedMicroTaskList final : public MicroTaskRunnable {
|
|
public:
|
|
SuppressedMicroTaskList() = delete;
|
|
explicit SuppressedMicroTaskList(CycleCollectedJSContext* aContext);
|
|
|
|
virtual bool Suppressed() override;
|
|
virtual void Run(AutoSlowOperation& aso) override {
|
|
// Does nothing; the only action occurs as part of the
|
|
// call to Suppressed().
|
|
}
|
|
|
|
CycleCollectedJSContext* mContext = nullptr;
|
|
uint64_t mSuppressionGeneration = 0;
|
|
JS::PersistentRooted<JS::GCVector<MustConsumeMicroTask>>
|
|
mSuppressedMicroTaskRunnables;
|
|
|
|
private:
|
|
~SuppressedMicroTaskList();
|
|
};
|
|
|
|
// Support for JS FinalizationRegistry objects, which allow a JS callback to be
|
|
// registered that is called when objects die.
|
|
//
|
|
// We keep a vector of functions that call back into the JS engine along
|
|
// with their associated incumbent globals, one per FinalizationRegistry object
|
|
// that has pending cleanup work. These are run in their own task.
|
|
class FinalizationRegistryCleanup {
|
|
public:
|
|
explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext);
|
|
void Init();
|
|
void Destroy();
|
|
void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal);
|
|
MOZ_CAN_RUN_SCRIPT void DoCleanup();
|
|
|
|
private:
|
|
static void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal,
|
|
void* aData);
|
|
|
|
class CleanupRunnable;
|
|
|
|
struct Callback {
|
|
JSFunction* mCallbackFunction;
|
|
JSObject* mIncumbentGlobal;
|
|
void trace(JSTracer* trc);
|
|
};
|
|
|
|
// This object is part of CycleCollectedJSContext, so it's safe to have a raw
|
|
// pointer to its containing context here.
|
|
CycleCollectedJSContext* mContext;
|
|
|
|
// Weak pointer to a previously dispatched runnable. The CleanupRunnable::Run
|
|
// method will null out this pointer.
|
|
CleanupRunnable* mPendingRunnable = nullptr;
|
|
|
|
using CallbackVector = JS::GCVector<Callback, 0, JSInfallibleAllocPolicy>;
|
|
JS::PersistentRooted<CallbackVector> mCallbacks;
|
|
};
|
|
|
|
bool EnqueueMicroTask(JSContext* aCx,
|
|
already_AddRefed<MicroTaskRunnable> aRunnable);
|
|
bool EnqueueDebugMicroTask(JSContext* aCx,
|
|
already_AddRefed<MicroTaskRunnable> aRunnable);
|
|
|
|
MustConsumeMicroTask DequeueNextMicroTask(JSContext* aCx);
|
|
MustConsumeMicroTask DequeueNextRegularMicroTask(JSContext* aCx);
|
|
MustConsumeMicroTask DequeueNextDebuggerMicroTask(JSContext* aCx);
|
|
|
|
WontConsumeMicroTask PeekNextMicroTask(JSContext* aCx);
|
|
|
|
class CycleCollectedJSContext : dom::PerThreadAtomCache, public JS::JobQueue {
|
|
friend class CycleCollectedJSRuntime;
|
|
friend class SuppressedMicroTasks;
|
|
friend class SuppressedMicroTaskList;
|
|
|
|
protected:
|
|
CycleCollectedJSContext();
|
|
virtual ~CycleCollectedJSContext();
|
|
|
|
MOZ_IS_CLASS_INIT
|
|
nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes);
|
|
|
|
virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0;
|
|
|
|
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
|
|
|
private:
|
|
static void PromiseRejectionTrackerCallback(
|
|
JSContext* aCx, bool aMutedErrors, JS::Handle<JSObject*> aPromise,
|
|
JS::PromiseRejectionHandlingState state, void* aData);
|
|
|
|
void AfterProcessMicrotasks();
|
|
|
|
public:
|
|
void ProcessStableStateQueue();
|
|
|
|
void ClearUncaughtRejectionObservers() {
|
|
mUncaughtRejectionObservers.Clear();
|
|
}
|
|
|
|
private:
|
|
void CleanupIDBTransactions(uint32_t aRecursionDepth);
|
|
|
|
public:
|
|
virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; }
|
|
virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; }
|
|
|
|
CycleCollectedJSRuntime* Runtime() const {
|
|
MOZ_ASSERT(mRuntime);
|
|
return mRuntime;
|
|
}
|
|
|
|
already_AddRefed<dom::Exception> GetPendingException() const;
|
|
void SetPendingException(dom::Exception* aException);
|
|
|
|
void TraceMicroTasks(JSTracer* aTracer);
|
|
|
|
JSContext* Context() const {
|
|
MOZ_ASSERT(mJSContext);
|
|
return mJSContext;
|
|
}
|
|
|
|
JS::RootingContext* RootingCx() const {
|
|
MOZ_ASSERT(mJSContext);
|
|
return JS::RootingContext::get(mJSContext);
|
|
}
|
|
|
|
void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) {
|
|
mTargetedMicroTaskRecursionDepth = aDepth;
|
|
}
|
|
|
|
void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; }
|
|
|
|
protected:
|
|
JSContext* MaybeContext() const { return mJSContext; }
|
|
|
|
public:
|
|
// nsThread entrypoints
|
|
//
|
|
// MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
|
|
// nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
|
|
// But we really should!
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
virtual void BeforeProcessTask(bool aMightBlock);
|
|
// MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
|
|
// nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
|
|
// But we really should!
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
virtual void AfterProcessTask(uint32_t aRecursionDepth);
|
|
|
|
// Check whether any eager thresholds have been reached, which would mean
|
|
// an idle GC task (minor or major) would be useful.
|
|
virtual void MaybePokeGC();
|
|
|
|
uint32_t RecursionDepth() const;
|
|
|
|
// Run in stable state (call through nsContentUtils)
|
|
void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
|
|
|
|
void AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction);
|
|
|
|
// Get the CycleCollectedJSContext for a JSContext.
|
|
// Returns null only if Initialize() has not completed on or during
|
|
// destruction of the CycleCollectedJSContext.
|
|
static CycleCollectedJSContext* GetFor(JSContext* aCx);
|
|
|
|
// Get the current thread's CycleCollectedJSContext. Returns null if there
|
|
// isn't one.
|
|
static CycleCollectedJSContext* Get();
|
|
|
|
// Queue an async microtask to the current main or worker thread.
|
|
virtual void DispatchToMicroTask(
|
|
already_AddRefed<MicroTaskRunnable> aRunnable);
|
|
|
|
// Call EnterMicroTask when you're entering JS execution.
|
|
// Usually the best way to do this is to use nsAutoMicroTask.
|
|
//
|
|
// Returns true if we expect to call PerformMicroTaskCheckpoint
|
|
// will occur on the next call to LeaveMicroTask
|
|
bool EnterMicroTask() { return (mMicroTaskLevel++ == 0); }
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
void LeaveMicroTask() {
|
|
if (--mMicroTaskLevel == 0) {
|
|
PerformMicroTaskCheckPoint();
|
|
}
|
|
}
|
|
|
|
uint32_t MicroTaskLevel() const { return mMicroTaskLevel; }
|
|
|
|
void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; }
|
|
|
|
void EnterSyncOperation() { ++mSyncOperations; }
|
|
void LeaveSyncOperation() { --mSyncOperations; }
|
|
bool IsInSyncOperation() const { return mSyncOperations > 0; }
|
|
|
|
// Track how many globals on this context's thread currently have a
|
|
// WebTaskSchedulingState, so that getHostDefinedData can skip
|
|
// walking the script settings stack in the common case where nothing has one.
|
|
void NoteWebTaskSchedulingStateAdded() { ++mWebTaskSchedulingStateCount; }
|
|
void NoteWebTaskSchedulingStateRemoved() {
|
|
MOZ_DIAGNOSTIC_ASSERT(mWebTaskSchedulingStateCount > 0,
|
|
"unbalanced WebTaskSchedulingState notification");
|
|
if (mWebTaskSchedulingStateCount > 0) {
|
|
--mWebTaskSchedulingStateCount;
|
|
}
|
|
}
|
|
bool MayHaveWebTaskSchedulingState() const {
|
|
return mWebTaskSchedulingStateCount != 0;
|
|
}
|
|
|
|
bool CheckRecursionDepth(uint32_t aCurrentDepth, bool aForce = false);
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
bool PerformMicroTaskCheckPoint(bool aForce = false);
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
void PerformDebuggerMicroTaskCheckpoint();
|
|
|
|
bool IsInStableOrMetaStableState() const { return mDoingStableStates; }
|
|
|
|
// Storage for watching rejected promises waiting for some client to
|
|
// consume their rejection.
|
|
// Promises in this list have been rejected in the last turn of the
|
|
// event loop without the rejection being handled.
|
|
// Note that this can contain nullptrs in place of promises removed because
|
|
// they're consumed before it'd be reported.
|
|
JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
|
|
mUncaughtRejections;
|
|
|
|
// Length past which mUncaughtRejections is searched through a companion
|
|
// index hashtable rather than scanned linearly.
|
|
static constexpr size_t kRejectedPromiseIndexThreshold = 8;
|
|
|
|
// Promise ID to its index in mUncaughtRejections. Only populated once that
|
|
// list grows past kRejectedPromiseIndexThreshold.
|
|
nsTHashMap<nsUint64HashKey, size_t> mUncaughtRejectionIndices;
|
|
|
|
// Promises in this list have previously been reported as rejected
|
|
// (because they were in the above list), but the rejection was handled
|
|
// in the last turn of the event loop.
|
|
JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
|
|
mConsumedRejections;
|
|
nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */>>
|
|
mUncaughtRejectionObservers;
|
|
|
|
bool HasPendingUnhandledRejection(uint64_t aPromiseID) const {
|
|
return mPendingUnhandledRejections.Contains(aPromiseID);
|
|
}
|
|
|
|
virtual bool IsSystemCaller() const = 0;
|
|
|
|
// Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads.
|
|
virtual void ReportError(JSErrorReport* aReport,
|
|
JS::ConstUTF8CharsZ aToStringResult) {
|
|
MOZ_ASSERT_UNREACHABLE("Not supported");
|
|
}
|
|
|
|
// These two functions control a special flag variable which lets us turn
|
|
// tracing on and off from a thread other than this JSContext's main thread.
|
|
// This is useful because we want to be able to start tracing many threads
|
|
// all at once from the Gecko Profiler in Firefox.
|
|
//
|
|
// NOTE: the caller must ensure that this CycleCollectedJSContext is not
|
|
// being destroyed when this is called. At the time of this API being added,
|
|
// the only consumer is the Gecko Profiler, which guarantees this via a mutex
|
|
// around unregistering the context, which always occurs before the context
|
|
// is destroyed.
|
|
void BeginExecutionTracingAsync();
|
|
void EndExecutionTracingAsync();
|
|
|
|
private:
|
|
// JS::JobQueue implementation: see js/public/Promise.h.
|
|
// SpiderMonkey uses some of these methods to enqueue promise resolution jobs.
|
|
// Others protect the debuggee microtask queue from the debugger's
|
|
// interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for
|
|
// details.
|
|
bool getHostDefinedData(
|
|
JSContext* aCx, JS::MutableHandle<JSObject*> aIncumbentGlobal,
|
|
JS::MutableHandle<JSObject*> aOptionalHostDefinedData) const override;
|
|
|
|
// Fills in the JS Object used to represent the current incumbent global.
|
|
// Used when running MicroTasks which don't have host-defined data as
|
|
// they will still need an incumbent global.
|
|
bool getHostDefinedGlobal(JSContext* cx,
|
|
JS::MutableHandle<JSObject*>) const override;
|
|
|
|
// MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey
|
|
// headers. The caller presumably knows this can run script (like everything
|
|
// in SpiderMonkey!) and will deal.
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
void runJobs(JSContext* cx) override;
|
|
|
|
bool isDrainingStopped() const override { return false; }
|
|
|
|
// Trace hook for non-GCThing microtask values (e.g., Private values
|
|
// containing MicroTaskRunnable pointers).
|
|
void traceNonGCThingMicroTask(JSTracer* trc, JS::Value* valuePtr) override;
|
|
|
|
class SavedMicroTaskQueue;
|
|
js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override;
|
|
|
|
private:
|
|
CycleCollectedJSRuntime* mRuntime;
|
|
|
|
JSContext* mJSContext;
|
|
|
|
nsCOMPtr<dom::Exception> mPendingException;
|
|
nsThread* mOwningThread; // Manual refcounting to avoid include hell.
|
|
|
|
struct PendingIDBTransactionData {
|
|
nsCOMPtr<nsIRunnable> mTransaction;
|
|
uint32_t mRecursionDepth;
|
|
};
|
|
|
|
nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
|
|
nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
|
|
uint32_t mBaseRecursionDepth;
|
|
bool mDoingStableStates;
|
|
|
|
// If set to none 0, microtasks will be processed only when recursion depth
|
|
// is the set value.
|
|
uint32_t mTargetedMicroTaskRecursionDepth;
|
|
|
|
uint32_t mMicroTaskLevel;
|
|
|
|
uint32_t mSyncOperations;
|
|
|
|
uint32_t mWebTaskSchedulingStateCount = 0;
|
|
|
|
RefPtr<SuppressedMicroTaskList> mSuppressedMicroTaskList;
|
|
|
|
uint64_t mSuppressionGeneration;
|
|
|
|
protected:
|
|
mozilla::LinkedList<MicroTaskRunnable> mMicrotasksToTrace;
|
|
|
|
private:
|
|
// How many times the debugger has interrupted execution, possibly creating
|
|
// microtask checkpoints in places that they would not normally occur.
|
|
uint32_t mDebuggerRecursionDepth;
|
|
|
|
Maybe<uint32_t> mMicroTaskRecursionDepth;
|
|
|
|
// This implements about-to-be-notified rejected promises list in the spec.
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
|
|
typedef nsTArray<RefPtr<dom::Promise>> PromiseArray;
|
|
PromiseArray mAboutToBeNotifiedRejectedPromises;
|
|
|
|
// This is for the "outstanding rejected promises weak set" in the spec,
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
|
|
// We use different data structure and opposite logic here to achieve the same
|
|
// effect. Basically this is used for tracking the rejected promise that does
|
|
// NOT need firing a rejectionhandled event. We will check the table to see if
|
|
// firing rejectionhandled event is required when a rejected promise is being
|
|
// handled.
|
|
//
|
|
// The rejected promise will be stored in the table if
|
|
// - it is unhandled, and
|
|
// - The unhandledrejection is not yet fired.
|
|
//
|
|
// And be removed when
|
|
// - it is handled, or
|
|
// - A unhandledrejection is fired and it isn't being handled in event
|
|
// handler.
|
|
struct PendingRejection {
|
|
RefPtr<dom::Promise> mPromise;
|
|
// Index of mPromise in mAboutToBeNotifiedRejectedPromises. That array is
|
|
// not compacted, so this stays valid until the array is emptied, after
|
|
// which it may name an unrelated entry and must be re-checked before use.
|
|
size_t mIndex = 0;
|
|
};
|
|
typedef nsTHashMap<nsUint64HashKey, PendingRejection> PromiseHashtable;
|
|
PromiseHashtable mPendingUnhandledRejections;
|
|
|
|
class NotifyUnhandledRejections final : public CancelableRunnable {
|
|
public:
|
|
explicit NotifyUnhandledRejections(PromiseArray&& aPromises)
|
|
: CancelableRunnable("NotifyUnhandledRejections"),
|
|
mUnhandledRejections(std::move(aPromises)) {}
|
|
|
|
NS_IMETHOD Run() final;
|
|
|
|
nsresult Cancel() final;
|
|
|
|
private:
|
|
PromiseArray mUnhandledRejections;
|
|
};
|
|
|
|
FinalizationRegistryCleanup mFinalizationRegistryCleanup;
|
|
};
|
|
|
|
class MOZ_STACK_CLASS nsAutoMicroTask {
|
|
public:
|
|
nsAutoMicroTask() {
|
|
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
|
if (ccjs) {
|
|
ccjs->EnterMicroTask();
|
|
}
|
|
}
|
|
MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() {
|
|
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
|
if (ccjs) {
|
|
ccjs->LeaveMicroTask();
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_CycleCollectedJSContext_h
|