1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
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 "nsIURI.h"
10 #include "nsIChannel.h"
11 #include "nsNetUtil.h"
12 #include "nsThreadUtils.h"
13 
14 #include "jsapi.h"
15 #include "jsfriendapi.h"
16 #include "js/Utility.h"
17 
18 #include "mozilla/dom/ChromeUtils.h"
19 #include "mozilla/dom/Promise.h"
20 #include "mozilla/dom/ScriptLoader.h"
21 #include "mozilla/HoldDropJSObjects.h"
22 #include "mozilla/SystemGroup.h"
23 #include "nsCycleCollectionParticipant.h"
24 
25 using namespace JS;
26 using namespace mozilla;
27 using namespace mozilla::dom;
28 
29 class AsyncScriptCompiler final : public nsIIncrementalStreamLoaderObserver,
30                                   public Runnable {
31  public:
32   // Note: References to this class are never held by cycle-collected objects.
33   // If at any point a reference is returned to a caller, please update this
34   // class to implement cycle collection.
35   NS_DECL_ISUPPORTS_INHERITED
36   NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
37   NS_DECL_NSIRUNNABLE
38 
AsyncScriptCompiler(JSContext * aCx,nsIGlobalObject * aGlobal,const nsACString & aURL,const CompileScriptOptionsDictionary & aOptions,Promise * aPromise)39   AsyncScriptCompiler(JSContext* aCx, nsIGlobalObject* aGlobal,
40                       const nsACString& aURL,
41                       const CompileScriptOptionsDictionary& aOptions,
42                       Promise* aPromise)
43       : mozilla::Runnable("AsyncScriptCompiler"),
44         mOptions(aCx),
45         mURL(aURL),
46         mGlobalObject(aGlobal),
47         mPromise(aPromise),
48         mCharset(aOptions.mCharset) {
49     mOptions.setNoScriptRval(!aOptions.mHasReturnValue)
50         .setCanLazilyParse(aOptions.mLazilyParse)
51         .setFile(aCx, mURL.get());
52   }
53 
54   nsresult Start(nsIPrincipal* aPrincipal);
55 
SetToken(void * aToken)56   inline void SetToken(void* aToken) { mToken = aToken; }
57 
58  protected:
~AsyncScriptCompiler()59   virtual ~AsyncScriptCompiler() {
60     if (mPromise->State() == Promise::PromiseState::Pending) {
61       mPromise->MaybeReject(NS_ERROR_FAILURE);
62     }
63   }
64 
65  private:
66   void Reject(JSContext* aCx);
67   void Reject(JSContext* aCx, const char* aMxg);
68 
69   bool StartCompile(JSContext* aCx);
70   void FinishCompile(JSContext* aCx);
71   void Finish(JSContext* aCx, Handle<JSScript*> script);
72 
73   OwningCompileOptions mOptions;
74   nsCString mURL;
75   nsCOMPtr<nsIGlobalObject> mGlobalObject;
76   RefPtr<Promise> mPromise;
77   nsString mCharset;
78   void* mToken;
79   UniqueTwoByteChars mScriptText;
80   size_t mScriptLength;
81 };
82 
NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler,Runnable,nsIIncrementalStreamLoaderObserver)83 NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler, Runnable,
84                                   nsIIncrementalStreamLoaderObserver)
85 NS_IMPL_ADDREF_INHERITED(AsyncScriptCompiler, Runnable)
86 NS_IMPL_RELEASE_INHERITED(AsyncScriptCompiler, Runnable)
87 
88 nsresult AsyncScriptCompiler::Start(nsIPrincipal* aPrincipal) {
89   nsCOMPtr<nsIURI> uri;
90   nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
91   NS_ENSURE_SUCCESS(rv, rv);
92 
93   nsCOMPtr<nsIChannel> channel;
94   rv = NS_NewChannel(getter_AddRefs(channel), uri, aPrincipal,
95                      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
96                      nsIContentPolicy::TYPE_OTHER);
97   NS_ENSURE_SUCCESS(rv, rv);
98 
99   nsCOMPtr<nsIIncrementalStreamLoader> loader;
100   rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), this);
101   NS_ENSURE_SUCCESS(rv, rv);
102 
103   return channel->AsyncOpen2(loader);
104 }
105 
OffThreadScriptLoaderCallback(void * aToken,void * aCallbackData)106 static void OffThreadScriptLoaderCallback(void* aToken, void* aCallbackData) {
107   RefPtr<AsyncScriptCompiler> scriptCompiler =
108       dont_AddRef(static_cast<AsyncScriptCompiler*>(aCallbackData));
109 
110   scriptCompiler->SetToken(aToken);
111 
112   SystemGroup::Dispatch(TaskCategory::Other, scriptCompiler.forget());
113 }
114 
StartCompile(JSContext * aCx)115 bool AsyncScriptCompiler::StartCompile(JSContext* aCx) {
116   Rooted<JSObject*> global(aCx, mGlobalObject->GetGlobalJSObject());
117 
118   if (JS::CanCompileOffThread(aCx, mOptions, mScriptLength)) {
119     if (!JS::CompileOffThread(aCx, mOptions, mScriptText.get(), mScriptLength,
120                               OffThreadScriptLoaderCallback,
121                               static_cast<void*>(this))) {
122       return false;
123     }
124 
125     NS_ADDREF(this);
126     return true;
127   }
128 
129   Rooted<JSScript*> script(aCx);
130   if (!JS::Compile(aCx, mOptions, mScriptText.get(), mScriptLength, &script)) {
131     return false;
132   }
133 
134   Finish(aCx, script);
135   return true;
136 }
137 
138 NS_IMETHODIMP
Run()139 AsyncScriptCompiler::Run() {
140   AutoJSAPI jsapi;
141   if (jsapi.Init(mGlobalObject)) {
142     FinishCompile(jsapi.cx());
143   } else {
144     jsapi.Init();
145     JS::CancelOffThreadScript(jsapi.cx(), mToken);
146 
147     mPromise->MaybeReject(NS_ERROR_FAILURE);
148   }
149 
150   return NS_OK;
151 }
152 
FinishCompile(JSContext * aCx)153 void AsyncScriptCompiler::FinishCompile(JSContext* aCx) {
154   Rooted<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, mToken));
155   if (script) {
156     Finish(aCx, script);
157   } else {
158     Reject(aCx);
159   }
160 }
161 
Finish(JSContext * aCx,Handle<JSScript * > aScript)162 void AsyncScriptCompiler::Finish(JSContext* aCx, Handle<JSScript*> aScript) {
163   RefPtr<PrecompiledScript> result =
164       new PrecompiledScript(mGlobalObject, aScript, mOptions);
165 
166   mPromise->MaybeResolve(result);
167 }
168 
Reject(JSContext * aCx)169 void AsyncScriptCompiler::Reject(JSContext* aCx) {
170   RootedValue value(aCx, JS::UndefinedValue());
171   if (JS_GetPendingException(aCx, &value)) {
172     JS_ClearPendingException(aCx);
173   }
174   mPromise->MaybeReject(aCx, value);
175 }
176 
Reject(JSContext * aCx,const char * aMsg)177 void AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg) {
178   nsAutoString msg;
179   msg.AppendASCII(aMsg);
180   msg.AppendLiteral(": ");
181   AppendUTF8toUTF16(mURL, msg);
182 
183   RootedValue exn(aCx);
184   if (xpc::NonVoidStringToJsval(aCx, msg, &exn)) {
185     JS_SetPendingException(aCx, exn);
186   }
187 
188   Reject(aCx);
189 }
190 
191 NS_IMETHODIMP
OnIncrementalData(nsIIncrementalStreamLoader * aLoader,nsISupports * aContext,uint32_t aDataLength,const uint8_t * aData,uint32_t * aConsumedData)192 AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
193                                        nsISupports* aContext,
194                                        uint32_t aDataLength,
195                                        const uint8_t* aData,
196                                        uint32_t* aConsumedData) {
197   return NS_OK;
198 }
199 
200 NS_IMETHODIMP
OnStreamComplete(nsIIncrementalStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aLength,const uint8_t * aBuf)201 AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
202                                       nsISupports* aContext, nsresult aStatus,
203                                       uint32_t aLength, const uint8_t* aBuf) {
204   AutoJSAPI jsapi;
205   if (!jsapi.Init(mGlobalObject)) {
206     mPromise->MaybeReject(NS_ERROR_FAILURE);
207     return NS_OK;
208   }
209 
210   JSContext* cx = jsapi.cx();
211 
212   if (NS_FAILED(aStatus)) {
213     Reject(cx, "Unable to load script");
214     return NS_OK;
215   }
216 
217   nsresult rv = ScriptLoader::ConvertToUTF16(
218       nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
219   if (NS_FAILED(rv)) {
220     Reject(cx, "Unable to decode script");
221     return NS_OK;
222   }
223 
224   if (!StartCompile(cx)) {
225     Reject(cx);
226   }
227 
228   return NS_OK;
229 }
230 
231 namespace mozilla {
232 namespace dom {
233 
CompileScript(GlobalObject & aGlobal,const nsAString & aURL,const CompileScriptOptionsDictionary & aOptions,ErrorResult & aRv)234 /* static */ already_AddRefed<Promise> ChromeUtils::CompileScript(
235     GlobalObject& aGlobal, const nsAString& aURL,
236     const CompileScriptOptionsDictionary& aOptions, ErrorResult& aRv) {
237   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
238   MOZ_ASSERT(global);
239 
240   RefPtr<Promise> promise = Promise::Create(global, aRv);
241   if (aRv.Failed()) {
242     return nullptr;
243   }
244 
245   NS_ConvertUTF16toUTF8 url(aURL);
246   RefPtr<AsyncScriptCompiler> compiler = new AsyncScriptCompiler(
247       aGlobal.Context(), global, url, aOptions, promise);
248 
249   nsresult rv = compiler->Start(aGlobal.GetSubjectPrincipal());
250   if (NS_FAILED(rv)) {
251     promise->MaybeReject(rv);
252   }
253 
254   return promise.forget();
255 }
256 
PrecompiledScript(nsISupports * aParent,Handle<JSScript * > aScript,JS::ReadOnlyCompileOptions & aOptions)257 PrecompiledScript::PrecompiledScript(nsISupports* aParent,
258                                      Handle<JSScript*> aScript,
259                                      JS::ReadOnlyCompileOptions& aOptions)
260     : mParent(aParent),
261       mScript(aScript),
262       mURL(aOptions.filename()),
263       mHasReturnValue(!aOptions.noScriptRval) {
264   MOZ_ASSERT(aParent);
265   MOZ_ASSERT(aScript);
266 
267   mozilla::HoldJSObjects(this);
268 };
269 
~PrecompiledScript()270 PrecompiledScript::~PrecompiledScript() { mozilla::DropJSObjects(this); }
271 
ExecuteInGlobal(JSContext * aCx,HandleObject aGlobal,MutableHandleValue aRval,ErrorResult & aRv)272 void PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
273                                         MutableHandleValue aRval,
274                                         ErrorResult& aRv) {
275   {
276     RootedObject targetObj(aCx, JS_FindCompilationScope(aCx, aGlobal));
277     JSAutoCompartment ac(aCx, targetObj);
278 
279     Rooted<JSScript*> script(aCx, mScript);
280     if (!JS::CloneAndExecuteScript(aCx, script, aRval)) {
281       aRv.NoteJSContextException(aCx);
282       return;
283     }
284   }
285 
286   JS_WrapValue(aCx, aRval);
287 }
288 
GetUrl(nsAString & aUrl)289 void PrecompiledScript::GetUrl(nsAString& aUrl) { CopyUTF8toUTF16(mURL, aUrl); }
290 
HasReturnValue()291 bool PrecompiledScript::HasReturnValue() { return mHasReturnValue; }
292 
WrapObject(JSContext * aCx,HandleObject aGivenProto)293 JSObject* PrecompiledScript::WrapObject(JSContext* aCx,
294                                         HandleObject aGivenProto) {
295   return PrecompiledScriptBinding::Wrap(aCx, this, aGivenProto);
296 }
297 
298 NS_IMPL_CYCLE_COLLECTION_CLASS(PrecompiledScript)
299 
300 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript)
301   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
302   NS_INTERFACE_MAP_ENTRY(nsISupports)
303 NS_INTERFACE_MAP_END
304 
305 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PrecompiledScript)
306   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
307   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
308 
309   tmp->mScript = nullptr;
310 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
311 
312 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PrecompiledScript)
313   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
314 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
315 
316 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PrecompiledScript)
317   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
318   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
319 NS_IMPL_CYCLE_COLLECTION_TRACE_END
320 
321 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript)
322 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript)
323 
324 }  // namespace dom
325 }  // namespace mozilla
326