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