Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / js / xpconnect / loader / ChromeScriptLoader.cpp
blob64de6c415fa694ac0c7cbb2bc086f12866304755
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"
10 #include "nsIURI.h"
11 #include "nsIChannel.h"
12 #include "nsNetUtil.h"
13 #include "nsThreadUtils.h"
15 #include "jsapi.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"
45 using namespace JS;
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
57 // beyond it.
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;
67 RunOnShutdown([] {
68 StaticMutexAutoLock lock(sOngoingTasksMutex);
69 for (auto* task : sOngoingTasks) {
70 task->Cancel();
72 });
75 StaticMutexAutoLock lock(sOngoingTasksMutex);
76 return sOngoingTasks.append(aTask);
79 static void UnregisterTask(const AsyncScriptCompileTask* aTask) {
80 StaticMutexAutoLock lock(sOngoingTasksMutex);
81 sOngoingTasks.eraseIfEqual(aTask);
84 public:
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);
95 UnregisterTask(this);
98 bool Init(const JS::OwningCompileOptions& aOptions) {
99 if (!RegisterTask(this)) {
100 return false;
103 mFrontendContext = JS::NewFrontendContext();
104 if (!mFrontendContext) {
105 return false;
108 if (!mOptions.copy(mFrontendContext, aOptions)) {
109 return false;
112 return true;
115 private:
116 void Compile() {
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));
122 mStencil =
123 JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, mSrcBuf);
126 // Cancel the task.
127 // If the task is already running, this waits for the task to finish.
128 void Cancel() {
129 MOZ_ASSERT(NS_IsMainThread());
131 MutexAutoLock lock(mMutex);
133 mIsCancelled = true;
135 mStencil = nullptr;
138 public:
139 TaskResult Run() override {
140 MutexAutoLock lock(mMutex);
142 if (mIsCancelled) {
143 return TaskResult::Complete;
146 Compile();
147 return TaskResult::Complete;
150 already_AddRefed<JS::Stencil> StealStencil(JSContext* aCx) {
151 JS::FrontendContext* fc = mFrontendContext;
152 mFrontendContext = nullptr;
154 MOZ_ASSERT(fc);
156 if (JS::HadFrontendErrors(fc)) {
157 (void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions);
158 JS::DestroyFrontendContext(fc);
159 return nullptr;
162 // Report warnings.
163 if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions)) {
164 JS::DestroyFrontendContext(fc);
165 return nullptr;
168 JS::DestroyFrontendContext(fc);
170 return mStencil.forget();
173 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
174 bool GetName(nsACString& aName) override {
175 aName.AssignLiteral("AsyncScriptCompileTask");
176 return true;
178 #endif
180 private:
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 {
207 public:
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");
219 return true;
221 #endif
223 TaskResult Run() override;
225 private:
226 // NOTE:
227 // This field is main-thread only, and this task shouldn't be freed off
228 // main thread.
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 {
241 public:
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.
245 NS_DECL_ISUPPORTS
246 NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
248 AsyncScriptCompiler(JSContext* aCx, nsIGlobalObject* aGlobal,
249 const nsACString& aURL, Promise* aPromise)
250 : mOptions(aCx),
251 mURL(aURL),
252 mGlobalObject(aGlobal),
253 mPromise(aPromise),
254 mScriptLength(0) {}
256 [[nodiscard]] nsresult Start(JSContext* aCx,
257 const CompileScriptOptionsDictionary& aOptions,
258 nsIPrincipal* aPrincipal);
260 void OnCompilationComplete(AsyncScriptCompileTask* aCompileTask);
262 protected:
263 virtual ~AsyncScriptCompiler() {
264 if (mPromise->State() == Promise::PromiseState::Pending) {
265 mPromise->MaybeReject(NS_ERROR_FAILURE);
269 private:
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;
279 nsCString mURL;
280 nsCOMPtr<nsIGlobalObject> mGlobalObject;
281 RefPtr<Promise> mPromise;
282 nsString mCharset;
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());
299 } else {
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;
317 rv = NS_NewChannel(
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)) {
336 return false;
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))) {
348 return false;
350 return true;
353 RefPtr<Stencil> stencil =
354 JS::CompileGlobalScriptToStencil(aCx, mOptions, srcBuf);
355 if (!stencil) {
356 return false;
359 Finish(aCx, std::move(stencil));
360 return true;
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)) {
372 return false;
375 complationCompleteTask->AddDependency(compileTask.get());
377 TaskController::Get()->AddTask(compileTask.forget());
378 TaskController::Get()->AddTask(complationCompleteTask.forget());
379 return true;
382 Task::TaskResult AsyncScriptCompilationCompleteTask::Run() {
383 mCompiler->OnCompilationComplete(mCompileTask.get());
384 mCompiler = nullptr;
385 mCompileTask = nullptr;
386 return TaskResult::Complete;
389 void AsyncScriptCompiler::OnCompilationComplete(
390 AsyncScriptCompileTask* aCompileTask) {
391 AutoJSAPI jsapi;
392 if (!jsapi.Init(mGlobalObject)) {
393 mPromise->MaybeReject(NS_ERROR_FAILURE);
394 return;
397 JSContext* cx = jsapi.cx();
398 RefPtr<JS::Stencil> stencil = aCompileTask->StealStencil(cx);
399 if (!stencil) {
400 Reject(cx);
401 return;
404 Finish(cx, std::move(stencil));
405 return;
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) {
425 nsAutoString msg;
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);
436 Reject(aCx);
439 NS_IMETHODIMP
440 AsyncScriptCompiler::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
442 NS_IMETHODIMP
443 AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
444 nsISupports* aContext,
445 uint32_t aDataLength,
446 const uint8_t* aData,
447 uint32_t* aConsumedData) {
448 return NS_OK;
451 NS_IMETHODIMP
452 AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
453 nsISupports* aContext, nsresult aStatus,
454 uint32_t aLength, const uint8_t* aBuf) {
455 AutoJSAPI jsapi;
456 if (!jsapi.Init(mGlobalObject)) {
457 mPromise->MaybeReject(NS_ERROR_FAILURE);
458 return NS_OK;
461 JSContext* cx = jsapi.cx();
463 if (NS_FAILED(aStatus)) {
464 Reject(cx, "Unable to load script");
465 return NS_OK;
468 nsresult rv = ScriptLoader::ConvertToUTF8(
469 nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
470 if (NS_FAILED(rv)) {
471 Reject(cx, "Unable to decode script");
472 return NS_OK;
475 if (!StartCompile(cx)) {
476 Reject(cx);
479 return NS_OK;
482 namespace mozilla {
483 namespace dom {
485 /* static */
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());
490 MOZ_ASSERT(global);
492 RefPtr<Promise> promise = Promise::Create(global, aRv);
493 if (aRv.Failed()) {
494 return nullptr;
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());
503 if (NS_FAILED(rv)) {
504 promise->MaybeReject(rv);
507 return promise.forget();
510 PrecompiledScript::PrecompiledScript(nsISupports* aParent,
511 RefPtr<JS::Stencil> aStencil,
512 JS::ReadOnlyCompileOptions& aOptions)
513 : mParent(aParent),
514 mStencil(aStencil),
515 mPublicURL(aOptions.filename().c_str()),
516 mHasReturnValue(!aOptions.noScriptRval) {
517 MOZ_ASSERT(aParent);
518 MOZ_ASSERT(aStencil);
519 #ifdef DEBUG
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();
524 #endif
527 void PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
528 const ExecuteInGlobalOptions& aOptions,
529 MutableHandleValue aRval,
530 ErrorResult& aRv) {
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
537 // console.
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));
545 if (!script) {
546 aRv.NoteJSContextException(aCx);
547 return;
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();
555 } else {
556 // Set the exception on our caller's cx.
557 aRv.MightThrowJSException();
558 aRv.StealExceptionFromJSContext(cx);
560 return;
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)
588 NS_INTERFACE_MAP_END
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)
605 } // namespace dom
606 } // namespace mozilla