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