/* -*- 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/. */
CycleCollectedJSContext::~CycleCollectedJSContext() {
MOZ_COUNT_DTOR(CycleCollectedJSContext); // If the allocation failed, here we are. if (!mJSContext) { return;
}
class PromiseJobRunnable final : public MicroTaskRunnable { public:
PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback,
JS::HandleObject aCallbackGlobal,
JS::HandleObject aAllocationSite,
nsIGlobalObject* aIncumbentGlobal)
: mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal,
aAllocationSite, aIncumbentGlobal)),
mPropagateUserInputEventHandling(false) {
MOZ_ASSERT(js::IsFunctionObject(aCallback));
if (aPromise) {
JS::PromiseUserInputEventHandlingState state =
JS::GetPromiseUserInputEventHandlingState(aPromise);
mPropagateUserInputEventHandling =
state ==
JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
}
}
virtual ~PromiseJobRunnable() = default;
protected:
MOZ_CAN_RUN_SCRIPT virtualvoid Run(AutoSlowOperation& aAso) override {
JSObject* callback = mCallback->CallbackPreserveColor();
nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; if (global && !global->IsDying()) { // Propagate the user input event handling bit if needed.
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
RefPtr<Document> doc; if (win) {
doc = win->GetExtantDoc();
}
AutoHandlingUserInputStatePusher userInpStatePusher(
mPropagateUserInputEventHandling);
mCallback->Call("promise callback");
aAso.CheckForInterrupt();
} // Now that mCallback is no longer needed, clear any pointers it contains to // JS GC things. This removes any storebuffer entries associated with those // pointers, which can cause problems by taking up memory and by triggering // minor GCs. This otherwise would not happen until the next minor GC or // cycle collection.
mCallback->Reset();
}
// Finalizer for instances of FinalizeHostDefinedData. // // HostDefinedData only contains incumbent global, no need to // clean that up. // TODO(sefeng): Bug 1929356 will add [[SchedulingState]] to HostDefinedData. void FinalizeHostDefinedData(JS::GCContext* gcx, JSObject* objSelf) {}
if (hostDefinedData) {
MOZ_RELEASE_ASSERT(JS::GetClass(hostDefinedData.get()) ==
&sHostDefinedDataClass);
JS::Value incumbentGlobal =
JS::GetReservedSlot(hostDefinedData.get(), INCUMBENT_SETTING_SLOT); // hostDefinedData is only created when incumbent global exists.
MOZ_ASSERT(incumbentGlobal.isObject());
global = xpc::NativeGlobal(&incumbentGlobal.toObject());
}
// Used only by the SpiderMonkey Debugger API, and even then only via // JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is // not affected; see comments in js/public/Promise.h. void CycleCollectedJSContext::runJobs(JSContext* aCx) {
MOZ_ASSERT(aCx == Context());
MOZ_ASSERT(Get() == this);
PerformMicroTaskCheckPoint();
}
bool CycleCollectedJSContext::empty() const { // This is our override of JS::JobQueue::empty. Since that interface is only // concerned with the ordinary microtask queue, not the debugger microtask // queue, we only report on the former. return mPendingMicroTaskRunnables.empty();
}
// Preserve a debuggee's microtask queue while it is interrupted by the // debugger. See the comments for JS::AutoDebuggerJobQueueInterruption. class CycleCollectedJSContext::SavedMicroTaskQueue
: public JS::JobQueue::SavedJobQueue { public: explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) {
ccjs->mDebuggerRecursionDepth++;
ccjs->mPendingMicroTaskRunnables.swap(mQueue);
}
~SavedMicroTaskQueue() { // The JS Debugger attempts to maintain the invariant that microtasks which // occur durring debugger operation are completely flushed from the task // queue before returning control to the debuggee, in order to avoid // micro-tasks generated during debugging from interfering with regular // operation. // // While the vast majority of microtasks can be reliably flushed, // synchronous operations (see nsAutoSyncOperation) such as printing and // alert diaglogs suppress the execution of some microtasks. // // When PerformMicroTaskCheckpoint is run while microtasks are suppressed, // any suppressed microtasks are gathered into a new SuppressedMicroTasks // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a // result, AutoDebuggerJobQueueInterruption::runJobs is not able to // correctly guarantee that the microtask queue is totally empty in the // presence of sync operations. // // Previous versions of this code release-asserted that the queue was empty, // causing user observable crashes (Bug 1849675). To avoid this, we instead // choose to move suspended microtasks from the SavedMicroTaskQueue to the // main microtask queue in this destructor. This means that jobs enqueued // during synchnronous events under debugger control may produce events // which run outside the debugger, but this is viewed as strictly // preferrable to crashing.
MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1);
MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth);
RefPtr<MicroTaskRunnable> maybeSuppressedTasks;
// Handle the case where there is a SuppressedMicroTask still in the queue. if (!ccjs->mPendingMicroTaskRunnables.empty()) {
maybeSuppressedTasks = ccjs->mPendingMicroTaskRunnables.front();
ccjs->mPendingMicroTaskRunnables.pop_front();
}
// Re-enqueue the suppressed task now that we've put the original microtask // queue back. if (maybeSuppressedTasks) {
ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks);
}
}
js::UniquePtr<JS::JobQueue::SavedJobQueue>
CycleCollectedJSContext::saveJobQueue(JSContext* cx) { auto saved = js::MakeUnique<SavedMicroTaskQueue>(this); if (!saved) { // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor // is never called, so mPendingMicroTaskRunnables is still initialized.
JS_ReportOutOfMemory(cx); return nullptr;
}
// When run, one event can add another event to the mStableStateEvents, as // such you can't use iterators here. for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
nsCOMPtr<nsIRunnable> event = std::move(mStableStateEvents[i]);
event->Run();
}
// If mPendingIDBTransactions has events in it now, they were added from // something we called, so they belong at the end of the queue.
localQueue.AppendElements(std::move(mPendingIDBTransactions));
mPendingIDBTransactions = std::move(localQueue);
mDoingStableStates = false;
}
void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) { // If ProcessNextEvent was called during a microtask callback, we // must process any pending microtasks before blocking in the event loop, // otherwise we may deadlock until an event enters the queue later. if (aMightBlock && PerformMicroTaskCheckPoint()) { // If any microtask was processed, we post a dummy event in order to // force the ProcessNextEvent call not to block. This is required // to support nested event loops implemented using a pattern like // "while (condition) thread.processNextEvent(true)", in case the // condition is triggered here by a Promise "then" callback.
NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
}
}
void CycleCollectedJSContext::MaybePokeGC() { // Worker-compatible check to see if we want to do an idle-time minor // GC. class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable { public: using mozilla::IdleRunnable::IdleRunnable;
if (Runtime()->IsIdleGCTaskNeeded()) {
nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
Runtime()->SetPendingIdleGCTask();
}
}
uint32_t CycleCollectedJSContext::RecursionDepth() const { // Debugger interruptions are included in the recursion depth so that debugger // microtask checkpoints do not run IDB transactions which were initiated // before the interruption. return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth;
}
// There must be an event running to get here. #ifndef MOZ_WIDGET_COCOA
MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth); #else // XXX bug 1261143 // Recursion depth should be greater than mBaseRecursionDepth, // or the runnable will stay in the queue forever. if (data.mRecursionDepth <= mBaseRecursionDepth) {
data.mRecursionDepth = mBaseRecursionDepth + 1;
} #endif
bool SuppressedMicroTasks::Suppressed() { if (mSuppressionGeneration == mContext->mSuppressionGeneration) { returntrue;
}
for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it =
mSuppressedMicroTaskRunnables.rbegin();
it != mSuppressedMicroTaskRunnables.rend(); ++it) {
mContext->GetMicroTaskQueue().push_front(*it);
}
mContext->mSuppressedMicroTasks = nullptr;
returnfalse;
}
bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
AfterProcessMicrotasks(); // Nothing to do, return early. returnfalse;
}
uint32_t currentDepth = RecursionDepth(); if (mMicroTaskRecursionDepth >= currentDepth && !aForce) { // We are already executing microtasks for the current recursion depth. returnfalse;
}
if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) { // Special case for main thread where DOM mutations may happen when // it is not safe to run scripts.
nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); returnfalse;
}
if (runnable->Suppressed()) { // Microtasks in worker shall never be suppressed. // Otherwise, mPendingMicroTaskRunnables will be replaced later with // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
MOZ_ASSERT(NS_IsMainThread());
JS::JobQueueMayNotBeEmpty(Context()); if (runnable != mSuppressedMicroTasks) { if (!mSuppressedMicroTasks) {
mSuppressedMicroTasks = new SuppressedMicroTasks(this);
}
mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back(
runnable);
}
} else { if (mPendingMicroTaskRunnables.empty() &&
mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) {
JS::JobQueueIsEmpty(Context());
}
didProcess = true;
// Put back the suppressed microtasks so that they will be run later. // Note, it is possible that we end up keeping these suppressed tasks around // for some time, but no longer than spinning the event loop nestedly // (sync XHR, alert, etc.) if (mSuppressedMicroTasks) {
mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks);
}
AfterProcessMicrotasks();
return didProcess;
}
void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() { // Don't do normal microtask handling checks here, since whoever is calling // this method is supposed to know what they are doing.
AutoSlowOperation aso; for (;;) { // For a debugger microtask checkpoint, we always use the debugger microtask // queue.
std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue =
&GetDebuggerMicroTaskQueue();
// Only fire unhandledrejection if the promise is still not handled;
uint64_t promiseID = JS::GetPromiseID(promiseObj); if (!JS::GetPromiseIsHandled(promiseObj)) { if (nsCOMPtr<EventTarget> target =
do_QueryInterface(promise->GetParentObject())) {
RootedDictionary<PromiseRejectionEventInit> init(cx);
init.mPromise = promiseObj;
init.mReason = JS::GetPromiseResult(promiseObj);
init.mCancelable = true;
RefPtr<PromiseRejectionEvent> event =
PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns,
init); // We don't use the result of dispatching event here to check whether to // report the Promise to console.
target->DispatchEvent(*event);
}
}
// If a rejected promise is being handled in "unhandledrejection" event // handler, it should be removed from the table in // PromiseRejectionTrackerCallback.
MOZ_ASSERT(!cccx->mPendingUnhandledRejections.Lookup(promiseID));
} return NS_OK;
}
void FinalizationRegistryCleanup::Destroy() { // This must happen before the CycleCollectedJSContext destructor calls // JS_DestroyContext().
mCallbacks.reset();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.