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/. */
7 #include "PrecompiledScript.h"
9 #include "nsIIncrementalStreamLoader.h"
11 #include "nsIChannel.h"
12 #include "nsNetUtil.h"
13 #include "nsThreadUtils.h"
16 #include "jsfriendapi.h"
17 #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions
18 #include "js/CompilationAndEvaluation.h"
19 #include "js/experimental/CompileScript.h" // JS::CompileGlobalScriptToStencil, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::HadFrontendErrors, JS::ConvertFrontendErrorsToRuntimeErrors
20 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileGlobalScriptToStencil, JS::InstantiateGlobalStencil
21 #include "js/SourceText.h" // JS::SourceText
22 #include "js/Utility.h"
24 #include "mozilla/AlreadyAddRefed.h" // already_AddRefed
25 #include "mozilla/Assertions.h" // MOZ_ASSERT
26 #include "mozilla/Attributes.h"
27 #include "mozilla/ClearOnShutdown.h" // RunOnShutdown
28 #include "mozilla/EventQueue.h" // EventQueuePriority
29 #include "mozilla/Mutex.h"
30 #include "mozilla/SchedulerGroup.h"
31 #include "mozilla/StaticMutex.h"
32 #include "mozilla/StaticPrefs_javascript.h"
33 #include "mozilla/dom/ChromeUtils.h"
34 #include "mozilla/dom/Promise.h"
35 #include "mozilla/dom/ScriptLoader.h"
36 #include "mozilla/HoldDropJSObjects.h"
37 #include "mozilla/RefPtr.h" // RefPtr
38 #include "mozilla/TaskController.h" // TaskController, Task
39 #include "mozilla/ThreadSafety.h" // MOZ_GUARDED_BY
40 #include "mozilla/Utf8.h" // Utf8Unit
41 #include "mozilla/Vector.h"
42 #include "nsCCUncollectableMarker.h"
43 #include "nsCycleCollectionParticipant.h"
46 using namespace mozilla
;
47 using namespace mozilla::dom
;
49 class AsyncScriptCompileTask final
: public Task
{
50 static mozilla::StaticMutex sOngoingTasksMutex
;
51 static Vector
<AsyncScriptCompileTask
*> sOngoingTasks
52 MOZ_GUARDED_BY(sOngoingTasksMutex
);
53 static bool sIsShutdownRegistered
;
55 // Compilation tasks should be cancelled before calling JS_ShutDown, in order
56 // to avoid keeping JS::Stencil and SharedImmutableString pointers alive
59 // Cancel all ongoing tasks at ShutdownPhase::XPCOMShutdownFinal, which
60 // happens before calling JS_ShutDown.
61 static bool RegisterTask(AsyncScriptCompileTask
* aTask
) {
62 MOZ_ASSERT(NS_IsMainThread());
64 if (!sIsShutdownRegistered
) {
65 sIsShutdownRegistered
= true;
68 StaticMutexAutoLock
lock(sOngoingTasksMutex
);
69 for (auto* task
: sOngoingTasks
) {
75 StaticMutexAutoLock
lock(sOngoingTasksMutex
);
76 return sOngoingTasks
.append(aTask
);
79 static void UnregisterTask(const AsyncScriptCompileTask
* aTask
) {
80 StaticMutexAutoLock
lock(sOngoingTasksMutex
);
81 sOngoingTasks
.eraseIfEqual(aTask
);
85 explicit AsyncScriptCompileTask(JS::SourceText
<Utf8Unit
>&& aSrcBuf
)
86 : Task(Kind::OffMainThreadOnly
, EventQueuePriority::Normal
),
87 mOptions(JS::OwningCompileOptions::ForFrontendContext()),
88 mSrcBuf(std::move(aSrcBuf
)),
89 mMutex("AsyncScriptCompileTask") {}
91 ~AsyncScriptCompileTask() {
92 if (mFrontendContext
) {
93 JS::DestroyFrontendContext(mFrontendContext
);
98 bool Init(const JS::OwningCompileOptions
& aOptions
) {
99 if (!RegisterTask(this)) {
103 mFrontendContext
= JS::NewFrontendContext();
104 if (!mFrontendContext
) {
108 if (!mOptions
.copy(mFrontendContext
, aOptions
)) {
117 // NOTE: The stack limit must be set from the same thread that compiles.
118 size_t stackSize
= TaskController::GetThreadStackSize();
119 JS::SetNativeStackQuota(mFrontendContext
,
120 JS::ThreadStackQuotaForSize(stackSize
));
123 JS::CompileGlobalScriptToStencil(mFrontendContext
, mOptions
, mSrcBuf
);
127 // If the task is already running, this waits for the task to finish.
129 MOZ_ASSERT(NS_IsMainThread());
131 MutexAutoLock
lock(mMutex
);
139 TaskResult
Run() override
{
140 MutexAutoLock
lock(mMutex
);
143 return TaskResult::Complete
;
147 return TaskResult::Complete
;
150 already_AddRefed
<JS::Stencil
> StealStencil(JSContext
* aCx
) {
151 JS::FrontendContext
* fc
= mFrontendContext
;
152 mFrontendContext
= nullptr;
156 if (JS::HadFrontendErrors(fc
)) {
157 (void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx
, fc
, mOptions
);
158 JS::DestroyFrontendContext(fc
);
163 if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx
, fc
, mOptions
)) {
164 JS::DestroyFrontendContext(fc
);
168 JS::DestroyFrontendContext(fc
);
170 return mStencil
.forget();
173 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
174 bool GetName(nsACString
& aName
) override
{
175 aName
.AssignLiteral("AsyncScriptCompileTask");
181 // Owning-pointer for the context associated with the script compilation.
183 // The context is allocated on main thread in Init method, and is freed on
184 // any thread in the destructor.
185 JS::FrontendContext
* mFrontendContext
= nullptr;
187 JS::OwningCompileOptions mOptions
;
189 RefPtr
<JS::Stencil
> mStencil
;
191 JS::SourceText
<Utf8Unit
> mSrcBuf
;
193 // This mutex is locked during running the task or cancelling task.
194 mozilla::Mutex mMutex
;
196 bool mIsCancelled
MOZ_GUARDED_BY(mMutex
) = false;
199 /* static */ mozilla::StaticMutex
AsyncScriptCompileTask::sOngoingTasksMutex
;
200 MOZ_RUNINIT
/* static */ Vector
<AsyncScriptCompileTask
*>
201 AsyncScriptCompileTask::sOngoingTasks
;
202 /* static */ bool AsyncScriptCompileTask::sIsShutdownRegistered
= false;
204 class AsyncScriptCompiler
;
206 class AsyncScriptCompilationCompleteTask
: public Task
{
208 AsyncScriptCompilationCompleteTask(AsyncScriptCompiler
* aCompiler
,
209 AsyncScriptCompileTask
* aCompileTask
)
210 : Task(Kind::MainThreadOnly
, EventQueuePriority::Normal
),
211 mCompiler(aCompiler
),
212 mCompileTask(aCompileTask
) {
213 MOZ_ASSERT(NS_IsMainThread());
216 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
217 bool GetName(nsACString
& aName
) override
{
218 aName
.AssignLiteral("AsyncScriptCompilationCompleteTask");
223 TaskResult
Run() override
;
227 // This field is main-thread only, and this task shouldn't be freed off
230 // This is guaranteed by not having off-thread tasks which depends on this
231 // task, because otherwise the off-thread task's mDependencies can be the
232 // last reference, which results in freeing this task off main thread.
234 // If such task is added, this field must be moved to separate storage.
235 RefPtr
<AsyncScriptCompiler
> mCompiler
;
237 RefPtr
<AsyncScriptCompileTask
> mCompileTask
;
240 class AsyncScriptCompiler final
: public nsIIncrementalStreamLoaderObserver
{
242 // Note: References to this class are never held by cycle-collected objects.
243 // If at any point a reference is returned to a caller, please update this
244 // class to implement cycle collection.
246 NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
248 AsyncScriptCompiler(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
249 const nsACString
& aURL
, Promise
* aPromise
)
252 mGlobalObject(aGlobal
),
256 [[nodiscard
]] nsresult
Start(JSContext
* aCx
,
257 const CompileScriptOptionsDictionary
& aOptions
,
258 nsIPrincipal
* aPrincipal
);
260 void OnCompilationComplete(AsyncScriptCompileTask
* aCompileTask
);
263 virtual ~AsyncScriptCompiler() {
264 if (mPromise
->State() == Promise::PromiseState::Pending
) {
265 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
270 void Reject(JSContext
* aCx
);
271 void Reject(JSContext
* aCx
, const char* aMxg
);
273 bool StartCompile(JSContext
* aCx
);
274 bool StartOffThreadCompile(JS::SourceText
<Utf8Unit
>&& aSrcBuf
);
275 void FinishCompile(JSContext
* aCx
);
276 void Finish(JSContext
* aCx
, RefPtr
<JS::Stencil
>&& aStencil
);
278 OwningCompileOptions mOptions
;
280 nsCOMPtr
<nsIGlobalObject
> mGlobalObject
;
281 RefPtr
<Promise
> mPromise
;
283 UniquePtr
<Utf8Unit
[], JS::FreePolicy
> mScriptText
;
284 size_t mScriptLength
;
287 NS_IMPL_ISUPPORTS(AsyncScriptCompiler
, nsIIncrementalStreamLoaderObserver
)
289 nsresult
AsyncScriptCompiler::Start(
290 JSContext
* aCx
, const CompileScriptOptionsDictionary
& aOptions
,
291 nsIPrincipal
* aPrincipal
) {
292 mCharset
= aOptions
.mCharset
;
294 CompileOptions
options(aCx
);
295 nsAutoCString filename
;
296 if (aOptions
.mFilename
.WasPassed()) {
297 filename
= NS_ConvertUTF16toUTF8(aOptions
.mFilename
.Value());
298 options
.setFile(filename
.get());
300 options
.setFile(mURL
.get());
302 options
.setNoScriptRval(!aOptions
.mHasReturnValue
);
304 if (!aOptions
.mLazilyParse
) {
305 options
.setForceFullParse();
308 if (NS_WARN_IF(!mOptions
.copy(aCx
, options
))) {
309 return NS_ERROR_OUT_OF_MEMORY
;
312 nsCOMPtr
<nsIURI
> uri
;
313 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), mURL
);
314 NS_ENSURE_SUCCESS(rv
, rv
);
316 nsCOMPtr
<nsIChannel
> channel
;
318 getter_AddRefs(channel
), uri
, aPrincipal
,
319 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
320 nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT
);
321 NS_ENSURE_SUCCESS(rv
, rv
);
323 // allow deprecated HTTP request from SystemPrincipal
324 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
325 loadInfo
->SetAllowDeprecatedSystemRequests(true);
326 nsCOMPtr
<nsIIncrementalStreamLoader
> loader
;
327 rv
= NS_NewIncrementalStreamLoader(getter_AddRefs(loader
), this);
328 NS_ENSURE_SUCCESS(rv
, rv
);
330 return channel
->AsyncOpen(loader
);
333 bool AsyncScriptCompiler::StartCompile(JSContext
* aCx
) {
334 JS::SourceText
<Utf8Unit
> srcBuf
;
335 if (!srcBuf
.init(aCx
, std::move(mScriptText
), mScriptLength
)) {
339 // TODO: This uses the same heuristics and the same threshold as the
340 // JS::CanCompileOffThread, but the heuristics needs to be updated to
341 // reflect the change regarding the Stencil API, and also the thread
342 // management on the consumer side (bug 1846388).
343 static constexpr size_t OffThreadMinimumTextLength
= 5 * 1000;
345 if (StaticPrefs::javascript_options_parallel_parsing() &&
346 mScriptLength
>= OffThreadMinimumTextLength
) {
347 if (!StartOffThreadCompile(std::move(srcBuf
))) {
353 RefPtr
<Stencil
> stencil
=
354 JS::CompileGlobalScriptToStencil(aCx
, mOptions
, srcBuf
);
359 Finish(aCx
, std::move(stencil
));
363 bool AsyncScriptCompiler::StartOffThreadCompile(
364 JS::SourceText
<Utf8Unit
>&& aSrcBuf
) {
365 RefPtr
<AsyncScriptCompileTask
> compileTask
=
366 new AsyncScriptCompileTask(std::move(aSrcBuf
));
368 RefPtr
<AsyncScriptCompilationCompleteTask
> complationCompleteTask
=
369 new AsyncScriptCompilationCompleteTask(this, compileTask
.get());
371 if (!compileTask
->Init(mOptions
)) {
375 complationCompleteTask
->AddDependency(compileTask
.get());
377 TaskController::Get()->AddTask(compileTask
.forget());
378 TaskController::Get()->AddTask(complationCompleteTask
.forget());
382 Task::TaskResult
AsyncScriptCompilationCompleteTask::Run() {
383 mCompiler
->OnCompilationComplete(mCompileTask
.get());
385 mCompileTask
= nullptr;
386 return TaskResult::Complete
;
389 void AsyncScriptCompiler::OnCompilationComplete(
390 AsyncScriptCompileTask
* aCompileTask
) {
392 if (!jsapi
.Init(mGlobalObject
)) {
393 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
397 JSContext
* cx
= jsapi
.cx();
398 RefPtr
<JS::Stencil
> stencil
= aCompileTask
->StealStencil(cx
);
404 Finish(cx
, std::move(stencil
));
408 void AsyncScriptCompiler::Finish(JSContext
* aCx
,
409 RefPtr
<JS::Stencil
>&& aStencil
) {
410 RefPtr
<PrecompiledScript
> result
=
411 new PrecompiledScript(mGlobalObject
, aStencil
, mOptions
);
413 mPromise
->MaybeResolve(result
);
416 void AsyncScriptCompiler::Reject(JSContext
* aCx
) {
417 RootedValue
value(aCx
, JS::UndefinedValue());
418 if (JS_GetPendingException(aCx
, &value
)) {
419 JS_ClearPendingException(aCx
);
421 mPromise
->MaybeReject(value
);
424 void AsyncScriptCompiler::Reject(JSContext
* aCx
, const char* aMsg
) {
426 msg
.AppendASCII(aMsg
);
427 msg
.AppendLiteral(": ");
428 nsDependentCString
filename(mOptions
.filename().c_str());
429 AppendUTF8toUTF16(filename
, msg
);
431 RootedValue
exn(aCx
);
432 if (xpc::NonVoidStringToJsval(aCx
, msg
, &exn
)) {
433 JS_SetPendingException(aCx
, exn
);
440 AsyncScriptCompiler::OnStartRequest(nsIRequest
* aRequest
) { return NS_OK
; }
443 AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader
* aLoader
,
444 nsISupports
* aContext
,
445 uint32_t aDataLength
,
446 const uint8_t* aData
,
447 uint32_t* aConsumedData
) {
452 AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader
* aLoader
,
453 nsISupports
* aContext
, nsresult aStatus
,
454 uint32_t aLength
, const uint8_t* aBuf
) {
456 if (!jsapi
.Init(mGlobalObject
)) {
457 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
461 JSContext
* cx
= jsapi
.cx();
463 if (NS_FAILED(aStatus
)) {
464 Reject(cx
, "Unable to load script");
468 nsresult rv
= ScriptLoader::ConvertToUTF8(
469 nullptr, aBuf
, aLength
, mCharset
, nullptr, mScriptText
, mScriptLength
);
471 Reject(cx
, "Unable to decode script");
475 if (!StartCompile(cx
)) {
486 already_AddRefed
<Promise
> ChromeUtils::CompileScript(
487 GlobalObject
& aGlobal
, const nsAString
& aURL
,
488 const CompileScriptOptionsDictionary
& aOptions
, ErrorResult
& aRv
) {
489 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
492 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
497 NS_ConvertUTF16toUTF8
url(aURL
);
498 RefPtr
<AsyncScriptCompiler
> compiler
=
499 new AsyncScriptCompiler(aGlobal
.Context(), global
, url
, promise
);
501 nsresult rv
= compiler
->Start(aGlobal
.Context(), aOptions
,
502 aGlobal
.GetSubjectPrincipal());
504 promise
->MaybeReject(rv
);
507 return promise
.forget();
510 PrecompiledScript::PrecompiledScript(nsISupports
* aParent
,
511 RefPtr
<JS::Stencil
> aStencil
,
512 JS::ReadOnlyCompileOptions
& aOptions
)
515 mPublicURL(aOptions
.filename().c_str()),
516 mHasReturnValue(!aOptions
.noScriptRval
) {
518 MOZ_ASSERT(aStencil
);
520 // AsyncScriptCompiler::Start can call JS::CompileOptions::setForceFullParse,
521 // but it should be compatible with the default JS::InstantiateOptions.
522 JS::InstantiateOptions
options(aOptions
);
523 options
.assertCompatibleWithDefault();
527 void PrecompiledScript::ExecuteInGlobal(JSContext
* aCx
, HandleObject aGlobal
,
528 const ExecuteInGlobalOptions
& aOptions
,
529 MutableHandleValue aRval
,
532 RootedObject
targetObj(aCx
, JS_FindCompilationScope(aCx
, aGlobal
));
533 // Use AutoEntryScript for its ReportException method call.
534 // This will ensure notified any exception happening in the content script
535 // directly to the console, so that exceptions are flagged with the right
536 // innerWindowID. It helps these exceptions to appear in the page's web
538 AutoEntryScript
aes(targetObj
, "pre-compiled-script execution");
539 JSContext
* cx
= aes
.cx();
541 // See assertion in constructor.
542 JS::InstantiateOptions options
;
543 Rooted
<JSScript
*> script(
544 cx
, JS::InstantiateGlobalStencil(cx
, options
, mStencil
));
546 aRv
.NoteJSContextException(aCx
);
550 if (!JS_ExecuteScript(cx
, script
, aRval
)) {
551 JS::RootedValue
exn(cx
);
552 if (aOptions
.mReportExceptions
) {
553 // Note that ReportException will consume the exception.
554 aes
.ReportException();
556 // Set the exception on our caller's cx.
557 aRv
.MightThrowJSException();
558 aRv
.StealExceptionFromJSContext(cx
);
564 JS_WrapValue(aCx
, aRval
);
567 void PrecompiledScript::GetUrl(nsAString
& aUrl
) {
568 CopyUTF8toUTF16(mPublicURL
, aUrl
);
571 bool PrecompiledScript::HasReturnValue() { return mHasReturnValue
; }
573 JSObject
* PrecompiledScript::WrapObject(JSContext
* aCx
,
574 HandleObject aGivenProto
) {
575 return PrecompiledScript_Binding::Wrap(aCx
, this, aGivenProto
);
578 bool PrecompiledScript::IsBlackForCC(bool aTracingNeeded
) {
579 return (nsCCUncollectableMarker::sGeneration
&& HasKnownLiveWrapper() &&
580 (!aTracingNeeded
|| HasNothingToTrace(this)));
583 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PrecompiledScript
, mParent
)
585 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript
)
586 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
587 NS_INTERFACE_MAP_ENTRY(nsISupports
)
590 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(PrecompiledScript
)
591 return tmp
->IsBlackForCC(false);
592 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
594 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(PrecompiledScript
)
595 return tmp
->IsBlackForCC(true);
596 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
598 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(PrecompiledScript
)
599 return tmp
->IsBlackForCC(false);
600 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
602 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript
)
603 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript
)
606 } // namespace mozilla