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_CycleCollectedJSContext_h 8 #define mozilla_CycleCollectedJSContext_h 9 10 #include <deque> 11 12 #include "mozilla/Attributes.h" 13 #include "mozilla/MemoryReporting.h" 14 #include "mozilla/dom/AtomList.h" 15 #include "mozilla/dom/Promise.h" 16 #include "js/GCVector.h" 17 #include "js/Promise.h" 18 19 #include "nsCOMPtr.h" 20 #include "nsRefPtrHashtable.h" 21 #include "nsTArray.h" 22 23 class nsCycleCollectionNoteRootCallback; 24 class nsIRunnable; 25 class nsThread; 26 27 namespace mozilla { 28 class AutoSlowOperation; 29 30 class CycleCollectedJSContext; 31 class CycleCollectedJSRuntime; 32 33 namespace dom { 34 class Exception; 35 class WorkerJSContext; 36 class WorkletJSContext; 37 } // namespace dom 38 39 // Contains various stats about the cycle collection. 40 struct CycleCollectorResults { CycleCollectorResultsCycleCollectorResults41 CycleCollectorResults() { 42 // Initialize here so when we increment mNumSlices the first time we're 43 // not using uninitialized memory. 44 Init(); 45 } 46 InitCycleCollectorResults47 void Init() { 48 mForcedGC = false; 49 mMergedZones = false; 50 mAnyManual = false; 51 mVisitedRefCounted = 0; 52 mVisitedGCed = 0; 53 mFreedRefCounted = 0; 54 mFreedGCed = 0; 55 mFreedJSZones = 0; 56 mNumSlices = 1; 57 // mNumSlices is initialized to one, because we call Init() after the 58 // per-slice increment of mNumSlices has already occurred. 59 } 60 61 bool mForcedGC; 62 bool mMergedZones; 63 // mAnyManual is true if any slice was manually triggered, and at shutdown. 64 bool mAnyManual; 65 uint32_t mVisitedRefCounted; 66 uint32_t mVisitedGCed; 67 uint32_t mFreedRefCounted; 68 uint32_t mFreedGCed; 69 uint32_t mFreedJSZones; 70 uint32_t mNumSlices; 71 }; 72 73 class MicroTaskRunnable { 74 public: 75 MicroTaskRunnable() = default; 76 NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable) 77 MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0; Suppressed()78 virtual bool Suppressed() { return false; } 79 80 protected: 81 virtual ~MicroTaskRunnable() = default; 82 }; 83 84 // Store the suppressed mictotasks in another microtask so that operations 85 // for the microtask queue as a whole keep working. 86 class SuppressedMicroTasks : public MicroTaskRunnable { 87 public: 88 explicit SuppressedMicroTasks(CycleCollectedJSContext* aContext); 89 Run(AutoSlowOperation & aAso)90 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {} 91 virtual bool Suppressed(); 92 93 CycleCollectedJSContext* mContext; 94 uint64_t mSuppressionGeneration; 95 std::deque<RefPtr<MicroTaskRunnable>> mSuppressedMicroTaskRunnables; 96 }; 97 98 // Support for JS FinalizationRegistry objects, which allow a JS callback to be 99 // registered that is called when objects die. 100 // 101 // We keep a vector of functions that call back into the JS engine along 102 // with their associated incumbent globals, one per FinalizationRegistry object 103 // that has pending cleanup work. These are run in their own task. 104 class FinalizationRegistryCleanup { 105 public: 106 explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext); 107 void Init(); 108 void Destroy(); 109 void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal); 110 MOZ_CAN_RUN_SCRIPT void DoCleanup(); 111 112 private: 113 static void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal, 114 void* aData); 115 116 class CleanupRunnable; 117 118 struct Callback { 119 JSFunction* mCallbackFunction; 120 JSObject* mIncumbentGlobal; 121 void trace(JSTracer* trc); 122 }; 123 124 // This object is part of CycleCollectedJSContext, so it's safe to have a raw 125 // pointer to its containing context here. 126 CycleCollectedJSContext* mContext; 127 128 using CallbackVector = JS::GCVector<Callback, 0, InfallibleAllocPolicy>; 129 JS::PersistentRooted<CallbackVector> mCallbacks; 130 }; 131 132 class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { 133 friend class CycleCollectedJSRuntime; 134 friend class SuppressedMicroTasks; 135 136 protected: 137 CycleCollectedJSContext(); 138 virtual ~CycleCollectedJSContext(); 139 140 MOZ_IS_CLASS_INIT 141 nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes); 142 143 virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0; 144 145 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; 146 147 private: 148 static JSObject* GetIncumbentGlobalCallback(JSContext* aCx); 149 static bool EnqueuePromiseJobCallback(JSContext* aCx, 150 JS::HandleObject aPromise, 151 JS::HandleObject aJob, 152 JS::HandleObject aAllocationSite, 153 JS::HandleObject aIncumbentGlobal, 154 void* aData); 155 static void PromiseRejectionTrackerCallback( 156 JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise, 157 JS::PromiseRejectionHandlingState state, void* aData); 158 159 void AfterProcessMicrotasks(); 160 161 public: 162 void ProcessStableStateQueue(); 163 164 private: 165 void CleanupIDBTransactions(uint32_t aRecursionDepth); 166 167 public: 168 enum DeferredFinalizeType { 169 FinalizeIncrementally, 170 FinalizeNow, 171 }; 172 GetAsWorkerJSContext()173 virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; } GetAsWorkletJSContext()174 virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; } 175 Runtime()176 CycleCollectedJSRuntime* Runtime() const { 177 MOZ_ASSERT(mRuntime); 178 return mRuntime; 179 } 180 181 already_AddRefed<dom::Exception> GetPendingException() const; 182 void SetPendingException(dom::Exception* aException); 183 184 std::deque<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue(); 185 std::deque<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue(); 186 Context()187 JSContext* Context() const { 188 MOZ_ASSERT(mJSContext); 189 return mJSContext; 190 } 191 RootingCx()192 JS::RootingContext* RootingCx() const { 193 MOZ_ASSERT(mJSContext); 194 return JS::RootingContext::get(mJSContext); 195 } 196 SetTargetedMicroTaskRecursionDepth(uint32_t aDepth)197 void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) { 198 mTargetedMicroTaskRecursionDepth = aDepth; 199 } 200 UpdateMicroTaskSuppressionGeneration()201 void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; } 202 203 protected: MaybeContext()204 JSContext* MaybeContext() const { return mJSContext; } 205 206 public: 207 // nsThread entrypoints 208 // 209 // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate 210 // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. 211 // But we really should! 212 MOZ_CAN_RUN_SCRIPT_BOUNDARY 213 virtual void BeforeProcessTask(bool aMightBlock); 214 // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate 215 // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. 216 // But we really should! 217 MOZ_CAN_RUN_SCRIPT_BOUNDARY 218 virtual void AfterProcessTask(uint32_t aRecursionDepth); 219 220 // Check whether we need an idle GC task. 221 void IsIdleGCTaskNeeded() const; 222 223 uint32_t RecursionDepth() const; 224 225 // Run in stable state (call through nsContentUtils) 226 void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable); 227 228 void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction); 229 230 // Get the CycleCollectedJSContext for a JSContext. 231 // Returns null only if Initialize() has not completed on or during 232 // destruction of the CycleCollectedJSContext. 233 static CycleCollectedJSContext* GetFor(JSContext* aCx); 234 235 // Get the current thread's CycleCollectedJSContext. Returns null if there 236 // isn't one. 237 static CycleCollectedJSContext* Get(); 238 239 // Queue an async microtask to the current main or worker thread. 240 virtual void DispatchToMicroTask( 241 already_AddRefed<MicroTaskRunnable> aRunnable); 242 243 // Call EnterMicroTask when you're entering JS execution. 244 // Usually the best way to do this is to use nsAutoMicroTask. EnterMicroTask()245 void EnterMicroTask() { ++mMicroTaskLevel; } 246 247 MOZ_CAN_RUN_SCRIPT LeaveMicroTask()248 void LeaveMicroTask() { 249 if (--mMicroTaskLevel == 0) { 250 PerformMicroTaskCheckPoint(); 251 } 252 } 253 IsInMicroTask()254 bool IsInMicroTask() const { return mMicroTaskLevel != 0; } 255 MicroTaskLevel()256 uint32_t MicroTaskLevel() const { return mMicroTaskLevel; } 257 SetMicroTaskLevel(uint32_t aLevel)258 void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; } 259 260 MOZ_CAN_RUN_SCRIPT 261 bool PerformMicroTaskCheckPoint(bool aForce = false); 262 263 MOZ_CAN_RUN_SCRIPT 264 void PerformDebuggerMicroTaskCheckpoint(); 265 IsInStableOrMetaStableState()266 bool IsInStableOrMetaStableState() const { return mDoingStableStates; } 267 268 // Storage for watching rejected promises waiting for some client to 269 // consume their rejection. 270 // Promises in this list have been rejected in the last turn of the 271 // event loop without the rejection being handled. 272 // Note that this can contain nullptrs in place of promises removed because 273 // they're consumed before it'd be reported. 274 JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> 275 mUncaughtRejections; 276 277 // Promises in this list have previously been reported as rejected 278 // (because they were in the above list), but the rejection was handled 279 // in the last turn of the event loop. 280 JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> 281 mConsumedRejections; 282 nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */>> 283 mUncaughtRejectionObservers; 284 285 virtual bool IsSystemCaller() const = 0; 286 287 // Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads. ReportError(JSErrorReport * aReport,JS::ConstUTF8CharsZ aToStringResult)288 virtual void ReportError(JSErrorReport* aReport, 289 JS::ConstUTF8CharsZ aToStringResult) { 290 MOZ_ASSERT_UNREACHABLE("Not supported"); 291 } 292 293 private: 294 // JS::JobQueue implementation: see js/public/Promise.h. 295 // SpiderMonkey uses some of these methods to enqueue promise resolution jobs. 296 // Others protect the debuggee microtask queue from the debugger's 297 // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for 298 // details. 299 JSObject* getIncumbentGlobal(JSContext* cx) override; 300 bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, 301 JS::HandleObject job, JS::HandleObject allocationSite, 302 JS::HandleObject incumbentGlobal) override; 303 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey 304 // headers. The caller presumably knows this can run script (like everything 305 // in SpiderMonkey!) and will deal. 306 MOZ_CAN_RUN_SCRIPT_BOUNDARY 307 void runJobs(JSContext* cx) override; 308 bool empty() const override; 309 class SavedMicroTaskQueue; 310 js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override; 311 312 private: 313 CycleCollectedJSRuntime* mRuntime; 314 315 JSContext* mJSContext; 316 317 nsCOMPtr<dom::Exception> mPendingException; 318 nsThread* mOwningThread; // Manual refcounting to avoid include hell. 319 320 struct PendingIDBTransactionData { 321 nsCOMPtr<nsIRunnable> mTransaction; 322 uint32_t mRecursionDepth; 323 }; 324 325 nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents; 326 nsTArray<PendingIDBTransactionData> mPendingIDBTransactions; 327 uint32_t mBaseRecursionDepth; 328 bool mDoingStableStates; 329 330 // If set to none 0, microtasks will be processed only when recursion depth 331 // is the set value. 332 uint32_t mTargetedMicroTaskRecursionDepth; 333 334 uint32_t mMicroTaskLevel; 335 336 std::deque<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; 337 std::deque<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue; 338 RefPtr<SuppressedMicroTasks> mSuppressedMicroTasks; 339 uint64_t mSuppressionGeneration; 340 341 // How many times the debugger has interrupted execution, possibly creating 342 // microtask checkpoints in places that they would not normally occur. 343 uint32_t mDebuggerRecursionDepth; 344 345 uint32_t mMicroTaskRecursionDepth; 346 347 // This implements about-to-be-notified rejected promises list in the spec. 348 // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list 349 typedef nsTArray<RefPtr<dom::Promise>> PromiseArray; 350 PromiseArray mAboutToBeNotifiedRejectedPromises; 351 352 // This is for the "outstanding rejected promises weak set" in the spec, 353 // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set 354 // We use different data structure and opposite logic here to achieve the same 355 // effect. Basically this is used for tracking the rejected promise that does 356 // NOT need firing a rejectionhandled event. We will check the table to see if 357 // firing rejectionhandled event is required when a rejected promise is being 358 // handled. 359 // 360 // The rejected promise will be stored in the table if 361 // - it is unhandled, and 362 // - The unhandledrejection is not yet fired. 363 // 364 // And be removed when 365 // - it is handled, or 366 // - A unhandledrejection is fired and it isn't being handled in event 367 // handler. 368 typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable; 369 PromiseHashtable mPendingUnhandledRejections; 370 371 class NotifyUnhandledRejections final : public CancelableRunnable { 372 public: NotifyUnhandledRejections(CycleCollectedJSContext * aCx,PromiseArray && aPromises)373 NotifyUnhandledRejections(CycleCollectedJSContext* aCx, 374 PromiseArray&& aPromises) 375 : CancelableRunnable("NotifyUnhandledRejections"), 376 mCx(aCx), 377 mUnhandledRejections(std::move(aPromises)) {} 378 379 NS_IMETHOD Run() final; 380 381 nsresult Cancel() final; 382 383 private: 384 CycleCollectedJSContext* mCx; 385 PromiseArray mUnhandledRejections; 386 }; 387 388 FinalizationRegistryCleanup mFinalizationRegistryCleanup; 389 }; 390 391 class MOZ_STACK_CLASS nsAutoMicroTask { 392 public: nsAutoMicroTask()393 nsAutoMicroTask() { 394 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); 395 if (ccjs) { 396 ccjs->EnterMicroTask(); 397 } 398 } ~nsAutoMicroTask()399 MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() { 400 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); 401 if (ccjs) { 402 ccjs->LeaveMicroTask(); 403 } 404 } 405 }; 406 407 } // namespace mozilla 408 409 #endif // mozilla_CycleCollectedJSContext_h 410