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/ProfileTimelineMarkerBinding.h"
22 : #include "mozilla/dom/Promise.h"
23 : #include "mozilla/dom/PromiseBinding.h"
24 : #include "mozilla/dom/PromiseDebugging.h"
25 : #include "mozilla/dom/ScriptSettings.h"
26 : #include "jsprf.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 "nsJSUtils.h"
36 : #include "nsWrapperCache.h"
37 : #include "nsStringBuffer.h"
38 :
39 : #ifdef MOZ_CRASHREPORTER
40 : #include "nsExceptionHandler.h"
41 : #endif
42 :
43 : #include "nsIException.h"
44 : #include "nsIPlatformInfo.h"
45 : #include "nsThread.h"
46 : #include "nsThreadUtils.h"
47 : #include "xpcpublic.h"
48 :
49 : using namespace mozilla;
50 : using namespace mozilla::dom;
51 :
52 : namespace mozilla {
53 :
54 4 : CycleCollectedJSContext::CycleCollectedJSContext()
55 : : mIsPrimaryContext(true)
56 : , mRuntime(nullptr)
57 : , mJSContext(nullptr)
58 : , mDoingStableStates(false)
59 4 : , mDisableMicroTaskCheckpoint(false)
60 : {
61 4 : MOZ_COUNT_CTOR(CycleCollectedJSContext);
62 8 : nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
63 4 : mOwningThread = thread.forget().downcast<nsThread>().take();
64 4 : MOZ_RELEASE_ASSERT(mOwningThread);
65 4 : }
66 :
67 0 : CycleCollectedJSContext::~CycleCollectedJSContext()
68 : {
69 0 : MOZ_COUNT_DTOR(CycleCollectedJSContext);
70 : // If the allocation failed, here we are.
71 0 : if (!mJSContext) {
72 0 : return;
73 : }
74 :
75 0 : mRuntime->RemoveContext(this);
76 :
77 0 : if (mIsPrimaryContext) {
78 0 : mRuntime->Shutdown(mJSContext);
79 : }
80 :
81 : // Last chance to process any events.
82 0 : ProcessMetastableStateQueue(mBaseRecursionDepth);
83 0 : MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
84 :
85 0 : ProcessStableStateQueue();
86 0 : MOZ_ASSERT(mStableStateEvents.IsEmpty());
87 :
88 : // Clear mPendingException first, since it might be cycle collected.
89 0 : mPendingException = nullptr;
90 :
91 0 : MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty());
92 0 : MOZ_ASSERT(mPromiseMicroTaskQueue.empty());
93 :
94 0 : mUncaughtRejections.reset();
95 0 : mConsumedRejections.reset();
96 :
97 0 : JS_DestroyContext(mJSContext);
98 0 : mJSContext = nullptr;
99 :
100 0 : if (mIsPrimaryContext) {
101 0 : nsCycleCollector_forgetJSContext();
102 : } else {
103 0 : nsCycleCollector_forgetNonPrimaryContext();
104 : }
105 :
106 0 : mozilla::dom::DestroyScriptSettings();
107 :
108 0 : mOwningThread->SetScriptObserver(nullptr);
109 0 : NS_RELEASE(mOwningThread);
110 :
111 0 : if (mIsPrimaryContext) {
112 0 : delete mRuntime;
113 : }
114 0 : mRuntime = nullptr;
115 0 : }
116 :
117 : void
118 4 : CycleCollectedJSContext::InitializeCommon()
119 : {
120 4 : mRuntime->AddContext(this);
121 :
122 4 : mOwningThread->SetScriptObserver(this);
123 : // The main thread has a base recursion depth of 0, workers of 1.
124 4 : mBaseRecursionDepth = RecursionDepth();
125 :
126 4 : NS_GetCurrentThread()->SetCanInvokeJS(true);
127 :
128 4 : JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback);
129 :
130 4 : JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this);
131 4 : JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this);
132 4 : mUncaughtRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
133 4 : mConsumedRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
134 4 : }
135 :
136 : nsresult
137 4 : CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
138 : uint32_t aMaxBytes,
139 : uint32_t aMaxNurseryBytes)
140 : {
141 4 : MOZ_ASSERT(!mJSContext);
142 :
143 4 : mozilla::dom::InitScriptSettings();
144 4 : mJSContext = JS_NewContext(aMaxBytes, aMaxNurseryBytes, aParentRuntime);
145 4 : if (!mJSContext) {
146 0 : return NS_ERROR_OUT_OF_MEMORY;
147 : }
148 :
149 4 : mRuntime = CreateRuntime(mJSContext);
150 :
151 4 : InitializeCommon();
152 :
153 4 : nsCycleCollector_registerJSContext(this);
154 :
155 4 : return NS_OK;
156 : }
157 :
158 : nsresult
159 0 : CycleCollectedJSContext::InitializeNonPrimary(CycleCollectedJSContext* aPrimaryContext)
160 : {
161 0 : MOZ_ASSERT(!mJSContext);
162 :
163 0 : mIsPrimaryContext = false;
164 :
165 0 : mozilla::dom::InitScriptSettings();
166 0 : mJSContext = JS_NewCooperativeContext(aPrimaryContext->mJSContext);
167 0 : if (!mJSContext) {
168 0 : return NS_ERROR_OUT_OF_MEMORY;
169 : }
170 :
171 0 : mRuntime = aPrimaryContext->mRuntime;
172 :
173 0 : InitializeCommon();
174 :
175 0 : nsCycleCollector_registerNonPrimaryContext(this);
176 :
177 0 : return NS_OK;
178 : }
179 :
180 : size_t
181 0 : CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
182 : {
183 0 : return 0;
184 : }
185 :
186 : class PromiseJobRunnable final : public Runnable
187 : {
188 : public:
189 235 : PromiseJobRunnable(JS::HandleObject aCallback,
190 : JS::HandleObject aAllocationSite,
191 : nsIGlobalObject* aIncumbentGlobal)
192 235 : : Runnable("PromiseJobRunnable")
193 : , mCallback(
194 470 : new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
195 : {
196 235 : }
197 :
198 470 : virtual ~PromiseJobRunnable()
199 235 : {
200 705 : }
201 :
202 : protected:
203 : NS_IMETHOD
204 235 : Run() override
205 : {
206 235 : JSObject* callback = mCallback->CallbackPreserveColor();
207 235 : nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
208 235 : if (global && !global->IsDying()) {
209 235 : mCallback->Call("promise callback");
210 : }
211 235 : return NS_OK;
212 : }
213 :
214 : private:
215 : RefPtr<PromiseJobCallback> mCallback;
216 : };
217 :
218 : /* static */
219 : JSObject*
220 427 : CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx)
221 : {
222 427 : nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
223 427 : if (global) {
224 427 : return global->GetGlobalJSObject();
225 : }
226 0 : return nullptr;
227 : }
228 :
229 : /* static */
230 : bool
231 235 : CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
232 : JS::HandleObject aJob,
233 : JS::HandleObject aAllocationSite,
234 : JS::HandleObject aIncumbentGlobal,
235 : void* aData)
236 : {
237 235 : CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
238 235 : MOZ_ASSERT(aCx == self->Context());
239 235 : MOZ_ASSERT(Get() == self);
240 :
241 235 : nsIGlobalObject* global = nullptr;
242 235 : if (aIncumbentGlobal) {
243 235 : global = xpc::NativeGlobal(aIncumbentGlobal);
244 : }
245 705 : nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
246 235 : self->DispatchToMicroTask(runnable.forget());
247 470 : return true;
248 : }
249 :
250 : /* static */
251 : void
252 0 : CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
253 : JS::HandleObject aPromise,
254 : PromiseRejectionHandlingState state,
255 : void* aData)
256 : {
257 : #ifdef DEBUG
258 0 : CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
259 : #endif // DEBUG
260 0 : MOZ_ASSERT(aCx == self->Context());
261 0 : MOZ_ASSERT(Get() == self);
262 :
263 0 : if (state == PromiseRejectionHandlingState::Unhandled) {
264 0 : PromiseDebugging::AddUncaughtRejection(aPromise);
265 : } else {
266 0 : PromiseDebugging::AddConsumedRejection(aPromise);
267 : }
268 0 : }
269 :
270 : already_AddRefed<nsIException>
271 3957 : CycleCollectedJSContext::GetPendingException() const
272 : {
273 3957 : MOZ_ASSERT(mJSContext);
274 :
275 7914 : nsCOMPtr<nsIException> out = mPendingException;
276 7914 : return out.forget();
277 : }
278 :
279 : void
280 15339 : CycleCollectedJSContext::SetPendingException(nsIException* aException)
281 : {
282 15339 : MOZ_ASSERT(mJSContext);
283 15339 : mPendingException = aException;
284 15339 : }
285 :
286 : std::queue<nsCOMPtr<nsIRunnable>>&
287 1324 : CycleCollectedJSContext::GetPromiseMicroTaskQueue()
288 : {
289 1324 : MOZ_ASSERT(mJSContext);
290 1324 : return mPromiseMicroTaskQueue;
291 : }
292 :
293 : std::queue<nsCOMPtr<nsIRunnable>>&
294 0 : CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue()
295 : {
296 0 : MOZ_ASSERT(mJSContext);
297 0 : return mDebuggerPromiseMicroTaskQueue;
298 : }
299 :
300 : void
301 1258 : CycleCollectedJSContext::ProcessStableStateQueue()
302 : {
303 1258 : MOZ_ASSERT(mJSContext);
304 1258 : MOZ_RELEASE_ASSERT(!mDoingStableStates);
305 1258 : mDoingStableStates = true;
306 :
307 1258 : for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
308 0 : nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget();
309 0 : event->Run();
310 : }
311 :
312 1258 : mStableStateEvents.Clear();
313 1258 : mDoingStableStates = false;
314 1258 : }
315 :
316 : void
317 1495 : CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
318 : {
319 1495 : MOZ_ASSERT(mJSContext);
320 1495 : MOZ_RELEASE_ASSERT(!mDoingStableStates);
321 1495 : mDoingStableStates = true;
322 :
323 2990 : nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
324 :
325 1495 : for (uint32_t i = 0; i < localQueue.Length(); ++i)
326 : {
327 0 : RunInMetastableStateData& data = localQueue[i];
328 0 : if (data.mRecursionDepth != aRecursionDepth) {
329 0 : continue;
330 : }
331 :
332 : {
333 0 : nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
334 0 : runnable->Run();
335 : }
336 :
337 0 : localQueue.RemoveElementAt(i--);
338 : }
339 :
340 : // If the queue has events in it now, they were added from something we called,
341 : // so they belong at the end of the queue.
342 1495 : localQueue.AppendElements(mMetastableStateEvents);
343 1495 : localQueue.SwapElements(mMetastableStateEvents);
344 1495 : mDoingStableStates = false;
345 1495 : }
346 :
347 : void
348 1258 : CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
349 : {
350 1258 : MOZ_ASSERT(mJSContext);
351 :
352 : // See HTML 6.1.4.2 Processing model
353 :
354 : // Execute any events that were waiting for a microtask to complete.
355 : // This is not (yet) in the spec.
356 1258 : ProcessMetastableStateQueue(aRecursionDepth);
357 :
358 : // Step 4.1: Execute microtasks.
359 1258 : if (!mDisableMicroTaskCheckpoint) {
360 1227 : if (NS_IsMainThread()) {
361 1227 : nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
362 1227 : Promise::PerformMicroTaskCheckpoint();
363 : } else {
364 0 : Promise::PerformWorkerMicroTaskCheckpoint();
365 : }
366 : }
367 :
368 : // Step 4.2 Execute any events that were waiting for a stable state.
369 1258 : ProcessStableStateQueue();
370 1258 : }
371 :
372 : void
373 237 : CycleCollectedJSContext::AfterProcessMicrotask()
374 : {
375 237 : MOZ_ASSERT(mJSContext);
376 237 : AfterProcessMicrotask(RecursionDepth());
377 237 : }
378 :
379 : void
380 237 : CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
381 : {
382 237 : MOZ_ASSERT(mJSContext);
383 :
384 : // Between microtasks, execute any events that were waiting for a microtask
385 : // to complete.
386 237 : ProcessMetastableStateQueue(aRecursionDepth);
387 237 : }
388 :
389 : uint32_t
390 307 : CycleCollectedJSContext::RecursionDepth()
391 : {
392 307 : return mOwningThread->RecursionDepth();
393 : }
394 :
395 : void
396 0 : CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable)
397 : {
398 0 : MOZ_ASSERT(mJSContext);
399 0 : mStableStateEvents.AppendElement(Move(aRunnable));
400 0 : }
401 :
402 : void
403 0 : CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
404 : {
405 0 : MOZ_ASSERT(mJSContext);
406 :
407 0 : RunInMetastableStateData data;
408 0 : data.mRunnable = aRunnable;
409 :
410 0 : MOZ_ASSERT(mOwningThread);
411 0 : data.mRecursionDepth = RecursionDepth();
412 :
413 : // There must be an event running to get here.
414 : #ifndef MOZ_WIDGET_COCOA
415 0 : MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
416 : #else
417 : // XXX bug 1261143
418 : // Recursion depth should be greater than mBaseRecursionDepth,
419 : // or the runnable will stay in the queue forever.
420 : if (data.mRecursionDepth <= mBaseRecursionDepth) {
421 : data.mRecursionDepth = mBaseRecursionDepth + 1;
422 : }
423 : #endif
424 :
425 0 : mMetastableStateEvents.AppendElement(Move(data));
426 0 : }
427 :
428 : void
429 237 : CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable)
430 : {
431 474 : RefPtr<nsIRunnable> runnable(aRunnable);
432 :
433 237 : MOZ_ASSERT(NS_IsMainThread());
434 237 : MOZ_ASSERT(runnable);
435 :
436 237 : mPromiseMicroTaskQueue.push(runnable.forget());
437 237 : }
438 :
439 : } // namespace mozilla
|