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