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 "PrecompiledScript.h"
8
9 #include "nsIIncrementalStreamLoader.h"
10 #include "nsIURI.h"
11 #include "nsIChannel.h"
12 #include "nsNetUtil.h"
13 #include "nsThreadUtils.h"
14
15 #include "jsapi.h"
16 #include "jsfriendapi.h"
17 #include "js/CompilationAndEvaluation.h"
18 #include "js/experimental/JSStencil.h" // JS::CompileGlobalScriptToStencil, JS::InstantiateGlobalStencil, JS::OffThreadCompileToStencil
19 #include "js/SourceText.h"
20 #include "js/Utility.h"
21
22 #include "mozilla/Attributes.h"
23 #include "mozilla/SchedulerGroup.h"
24 #include "mozilla/dom/ChromeUtils.h"
25 #include "mozilla/dom/Promise.h"
26 #include "mozilla/dom/ScriptLoader.h"
27 #include "mozilla/HoldDropJSObjects.h"
28 #include "nsCCUncollectableMarker.h"
29 #include "nsCycleCollectionParticipant.h"
30
31 using namespace JS;
32 using namespace mozilla;
33 using namespace mozilla::dom;
34
35 class AsyncScriptCompiler final : public nsIIncrementalStreamLoaderObserver,
36 public Runnable {
37 public:
38 // Note: References to this class are never held by cycle-collected objects.
39 // If at any point a reference is returned to a caller, please update this
40 // class to implement cycle collection.
41 NS_DECL_ISUPPORTS_INHERITED
42 NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
43 NS_DECL_NSIRUNNABLE
44
AsyncScriptCompiler(JSContext * aCx,nsIGlobalObject * aGlobal,const nsACString & aURL,Promise * aPromise)45 AsyncScriptCompiler(JSContext* aCx, nsIGlobalObject* aGlobal,
46 const nsACString& aURL, Promise* aPromise)
47 : mozilla::Runnable("AsyncScriptCompiler"),
48 mOptions(aCx),
49 mURL(aURL),
50 mGlobalObject(aGlobal),
51 mPromise(aPromise),
52 mToken(nullptr),
53 mScriptLength(0) {}
54
55 [[nodiscard]] nsresult Start(JSContext* aCx,
56 const CompileScriptOptionsDictionary& aOptions,
57 nsIPrincipal* aPrincipal);
58
SetToken(JS::OffThreadToken * aToken)59 inline void SetToken(JS::OffThreadToken* aToken) { mToken = aToken; }
60
61 protected:
~AsyncScriptCompiler()62 virtual ~AsyncScriptCompiler() {
63 if (mPromise->State() == Promise::PromiseState::Pending) {
64 mPromise->MaybeReject(NS_ERROR_FAILURE);
65 }
66 }
67
68 private:
69 void Reject(JSContext* aCx);
70 void Reject(JSContext* aCx, const char* aMxg);
71
72 bool StartCompile(JSContext* aCx);
73 void FinishCompile(JSContext* aCx);
74 void Finish(JSContext* aCx, RefPtr<JS::Stencil> aStencil);
75
76 OwningCompileOptions mOptions;
77 nsCString mURL;
78 nsCOMPtr<nsIGlobalObject> mGlobalObject;
79 RefPtr<Promise> mPromise;
80 nsString mCharset;
81 JS::OffThreadToken* mToken;
82 UniqueTwoByteChars mScriptText;
83 size_t mScriptLength;
84 };
85
NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler,Runnable,nsIIncrementalStreamLoaderObserver)86 NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler, Runnable,
87 nsIIncrementalStreamLoaderObserver)
88 NS_IMPL_ADDREF_INHERITED(AsyncScriptCompiler, Runnable)
89 NS_IMPL_RELEASE_INHERITED(AsyncScriptCompiler, Runnable)
90
91 nsresult AsyncScriptCompiler::Start(
92 JSContext* aCx, const CompileScriptOptionsDictionary& aOptions,
93 nsIPrincipal* aPrincipal) {
94 mCharset = aOptions.mCharset;
95
96 CompileOptions options(aCx);
97 options.setFile(mURL.get()).setNoScriptRval(!aOptions.mHasReturnValue);
98
99 if (!aOptions.mLazilyParse) {
100 options.setForceFullParse();
101 }
102
103 if (NS_WARN_IF(!mOptions.copy(aCx, options))) {
104 return NS_ERROR_OUT_OF_MEMORY;
105 }
106
107 nsCOMPtr<nsIURI> uri;
108 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
109 NS_ENSURE_SUCCESS(rv, rv);
110
111 nsCOMPtr<nsIChannel> channel;
112 rv = NS_NewChannel(
113 getter_AddRefs(channel), uri, aPrincipal,
114 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
115 nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT);
116 NS_ENSURE_SUCCESS(rv, rv);
117
118 // allow deprecated HTTP request from SystemPrincipal
119 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
120 loadInfo->SetAllowDeprecatedSystemRequests(true);
121 nsCOMPtr<nsIIncrementalStreamLoader> loader;
122 rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), this);
123 NS_ENSURE_SUCCESS(rv, rv);
124
125 return channel->AsyncOpen(loader);
126 }
127
OffThreadScriptLoaderCallback(JS::OffThreadToken * aToken,void * aCallbackData)128 static void OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken,
129 void* aCallbackData) {
130 RefPtr<AsyncScriptCompiler> scriptCompiler =
131 dont_AddRef(static_cast<AsyncScriptCompiler*>(aCallbackData));
132
133 scriptCompiler->SetToken(aToken);
134
135 SchedulerGroup::Dispatch(TaskCategory::Other, scriptCompiler.forget());
136 }
137
StartCompile(JSContext * aCx)138 bool AsyncScriptCompiler::StartCompile(JSContext* aCx) {
139 JS::SourceText<char16_t> srcBuf;
140 if (!srcBuf.init(aCx, std::move(mScriptText), mScriptLength)) {
141 return false;
142 }
143
144 if (JS::CanCompileOffThread(aCx, mOptions, mScriptLength)) {
145 if (!JS::CompileToStencilOffThread(aCx, mOptions, srcBuf,
146 OffThreadScriptLoaderCallback,
147 static_cast<void*>(this))) {
148 return false;
149 }
150
151 NS_ADDREF(this);
152 return true;
153 }
154
155 RefPtr<Stencil> stencil =
156 JS::CompileGlobalScriptToStencil(aCx, mOptions, srcBuf);
157 if (!stencil) {
158 return false;
159 }
160
161 Finish(aCx, stencil);
162 return true;
163 }
164
165 NS_IMETHODIMP
Run()166 AsyncScriptCompiler::Run() {
167 AutoJSAPI jsapi;
168 if (jsapi.Init(mGlobalObject)) {
169 FinishCompile(jsapi.cx());
170 } else {
171 jsapi.Init();
172 JS::CancelOffThreadCompileToStencil(jsapi.cx(), mToken);
173
174 mPromise->MaybeReject(NS_ERROR_FAILURE);
175 }
176
177 return NS_OK;
178 }
179
FinishCompile(JSContext * aCx)180 void AsyncScriptCompiler::FinishCompile(JSContext* aCx) {
181 RefPtr<JS::Stencil> stencil =
182 JS::FinishOffThreadCompileToStencil(aCx, mToken);
183 if (stencil) {
184 Finish(aCx, stencil);
185 } else {
186 Reject(aCx);
187 }
188 }
189
Finish(JSContext * aCx,RefPtr<JS::Stencil> aStencil)190 void AsyncScriptCompiler::Finish(JSContext* aCx, RefPtr<JS::Stencil> aStencil) {
191 RefPtr<PrecompiledScript> result =
192 new PrecompiledScript(mGlobalObject, aStencil, mOptions);
193
194 mPromise->MaybeResolve(result);
195 }
196
Reject(JSContext * aCx)197 void AsyncScriptCompiler::Reject(JSContext* aCx) {
198 RootedValue value(aCx, JS::UndefinedValue());
199 if (JS_GetPendingException(aCx, &value)) {
200 JS_ClearPendingException(aCx);
201 }
202 mPromise->MaybeReject(value);
203 }
204
Reject(JSContext * aCx,const char * aMsg)205 void AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg) {
206 nsAutoString msg;
207 msg.AppendASCII(aMsg);
208 msg.AppendLiteral(": ");
209 AppendUTF8toUTF16(mURL, msg);
210
211 RootedValue exn(aCx);
212 if (xpc::NonVoidStringToJsval(aCx, msg, &exn)) {
213 JS_SetPendingException(aCx, exn);
214 }
215
216 Reject(aCx);
217 }
218
219 NS_IMETHODIMP
OnIncrementalData(nsIIncrementalStreamLoader * aLoader,nsISupports * aContext,uint32_t aDataLength,const uint8_t * aData,uint32_t * aConsumedData)220 AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
221 nsISupports* aContext,
222 uint32_t aDataLength,
223 const uint8_t* aData,
224 uint32_t* aConsumedData) {
225 return NS_OK;
226 }
227
228 NS_IMETHODIMP
OnStreamComplete(nsIIncrementalStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aLength,const uint8_t * aBuf)229 AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
230 nsISupports* aContext, nsresult aStatus,
231 uint32_t aLength, const uint8_t* aBuf) {
232 AutoJSAPI jsapi;
233 if (!jsapi.Init(mGlobalObject)) {
234 mPromise->MaybeReject(NS_ERROR_FAILURE);
235 return NS_OK;
236 }
237
238 JSContext* cx = jsapi.cx();
239
240 if (NS_FAILED(aStatus)) {
241 Reject(cx, "Unable to load script");
242 return NS_OK;
243 }
244
245 nsresult rv = ScriptLoader::ConvertToUTF16(
246 nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
247 if (NS_FAILED(rv)) {
248 Reject(cx, "Unable to decode script");
249 return NS_OK;
250 }
251
252 if (!StartCompile(cx)) {
253 Reject(cx);
254 }
255
256 return NS_OK;
257 }
258
259 namespace mozilla {
260 namespace dom {
261
262 /* static */
CompileScript(GlobalObject & aGlobal,const nsAString & aURL,const CompileScriptOptionsDictionary & aOptions,ErrorResult & aRv)263 already_AddRefed<Promise> ChromeUtils::CompileScript(
264 GlobalObject& aGlobal, const nsAString& aURL,
265 const CompileScriptOptionsDictionary& aOptions, ErrorResult& aRv) {
266 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
267 MOZ_ASSERT(global);
268
269 RefPtr<Promise> promise = Promise::Create(global, aRv);
270 if (aRv.Failed()) {
271 return nullptr;
272 }
273
274 NS_ConvertUTF16toUTF8 url(aURL);
275 RefPtr<AsyncScriptCompiler> compiler =
276 new AsyncScriptCompiler(aGlobal.Context(), global, url, promise);
277
278 nsresult rv = compiler->Start(aGlobal.Context(), aOptions,
279 aGlobal.GetSubjectPrincipal());
280 if (NS_FAILED(rv)) {
281 promise->MaybeReject(rv);
282 }
283
284 return promise.forget();
285 }
286
PrecompiledScript(nsISupports * aParent,RefPtr<JS::Stencil> aStencil,JS::ReadOnlyCompileOptions & aOptions)287 PrecompiledScript::PrecompiledScript(nsISupports* aParent,
288 RefPtr<JS::Stencil> aStencil,
289 JS::ReadOnlyCompileOptions& aOptions)
290 : mParent(aParent),
291 mStencil(aStencil),
292 mURL(aOptions.filename()),
293 mHasReturnValue(!aOptions.noScriptRval),
294 mLazilyParse(!aOptions.forceFullParse()) {
295 MOZ_ASSERT(aParent);
296 MOZ_ASSERT(aStencil);
297 };
298
ExecuteInGlobal(JSContext * aCx,HandleObject aGlobal,MutableHandleValue aRval,ErrorResult & aRv)299 void PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
300 MutableHandleValue aRval,
301 ErrorResult& aRv) {
302 {
303 RootedObject targetObj(aCx, JS_FindCompilationScope(aCx, aGlobal));
304 JSAutoRealm ar(aCx, targetObj);
305
306 CompileOptions options(aCx);
307 if (!mLazilyParse) {
308 // See AsyncScriptCompiler::Start.
309 options.setForceFullParse();
310 }
311 // mHasReturnValue (noScriptRval) is unused during instantiation.
312
313 Rooted<JSScript*> script(
314 aCx, JS::InstantiateGlobalStencil(aCx, options, mStencil));
315 if (!script) {
316 aRv.NoteJSContextException(aCx);
317 return;
318 }
319
320 if (!JS_ExecuteScript(aCx, script, aRval)) {
321 aRv.NoteJSContextException(aCx);
322 return;
323 }
324 }
325
326 JS_WrapValue(aCx, aRval);
327 }
328
GetUrl(nsAString & aUrl)329 void PrecompiledScript::GetUrl(nsAString& aUrl) { CopyUTF8toUTF16(mURL, aUrl); }
330
HasReturnValue()331 bool PrecompiledScript::HasReturnValue() { return mHasReturnValue; }
332
WrapObject(JSContext * aCx,HandleObject aGivenProto)333 JSObject* PrecompiledScript::WrapObject(JSContext* aCx,
334 HandleObject aGivenProto) {
335 return PrecompiledScript_Binding::Wrap(aCx, this, aGivenProto);
336 }
337
IsBlackForCC(bool aTracingNeeded)338 bool PrecompiledScript::IsBlackForCC(bool aTracingNeeded) {
339 return (nsCCUncollectableMarker::sGeneration && HasKnownLiveWrapper() &&
340 (!aTracingNeeded || HasNothingToTrace(this)));
341 }
342
343 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PrecompiledScript, mParent)
344
345 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript)
346 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
347 NS_INTERFACE_MAP_ENTRY(nsISupports)
348 NS_INTERFACE_MAP_END
349
350 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(PrecompiledScript)
351 return tmp->IsBlackForCC(false);
352 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
353
354 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(PrecompiledScript)
355 return tmp->IsBlackForCC(true);
356 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
357
358 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(PrecompiledScript)
359 return tmp->IsBlackForCC(false);
360 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
361
362 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript)
363 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript)
364
365 } // namespace dom
366 } // namespace mozilla
367