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 "mozJSSubScriptLoader.h"
8 : #include "mozJSComponentLoader.h"
9 : #include "mozJSLoaderUtils.h"
10 :
11 : #include "nsIURI.h"
12 : #include "nsIIOService.h"
13 : #include "nsIChannel.h"
14 : #include "nsIInputStream.h"
15 : #include "nsNetCID.h"
16 : #include "nsNetUtil.h"
17 : #include "nsIFileURL.h"
18 : #include "nsIScriptSecurityManager.h"
19 : #include "nsThreadUtils.h"
20 :
21 : #include "jsapi.h"
22 : #include "jsfriendapi.h"
23 : #include "nsJSPrincipals.h"
24 : #include "xpcprivate.h" // For xpc::OptionsBase
25 : #include "jswrapper.h"
26 :
27 : #include "mozilla/dom/Promise.h"
28 : #include "mozilla/dom/ToJSValue.h"
29 : #include "mozilla/dom/ScriptLoader.h"
30 : #include "mozilla/HoldDropJSObjects.h"
31 : #include "mozilla/ScriptPreloader.h"
32 : #include "mozilla/scache/StartupCache.h"
33 : #include "mozilla/scache/StartupCacheUtils.h"
34 : #include "mozilla/Unused.h"
35 : #include "nsContentUtils.h"
36 : #include "nsStringGlue.h"
37 : #include "nsCycleCollectionParticipant.h"
38 : #include "GeckoProfiler.h"
39 :
40 : using namespace mozilla::scache;
41 : using namespace JS;
42 : using namespace xpc;
43 : using namespace mozilla;
44 : using namespace mozilla::dom;
45 :
46 33 : class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase {
47 : public:
48 33 : explicit LoadSubScriptOptions(JSContext* cx = xpc_GetSafeJSContext(),
49 : JSObject* options = nullptr)
50 33 : : OptionsBase(cx, options)
51 : , target(cx)
52 33 : , charset(NullString())
53 : , ignoreCache(false)
54 : , async(false)
55 66 : , wantReturnValue(false)
56 33 : { }
57 :
58 0 : virtual bool Parse() {
59 0 : return ParseObject("target", &target) &&
60 0 : ParseString("charset", charset) &&
61 0 : ParseBoolean("ignoreCache", &ignoreCache) &&
62 0 : ParseBoolean("async", &async) &&
63 0 : ParseBoolean("wantReturnValue", &wantReturnValue);
64 : }
65 :
66 : RootedObject target;
67 : nsString charset;
68 : bool ignoreCache;
69 : bool async;
70 : bool wantReturnValue;
71 : };
72 :
73 :
74 : /* load() error msgs, XXX localize? */
75 : #define LOAD_ERROR_NOSERVICE "Error creating IO Service."
76 : #define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)"
77 : #define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad."
78 : #define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI."
79 : #define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)"
80 : #define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)"
81 : #define LOAD_ERROR_BADCHARSET "Error converting to specified charset"
82 : #define LOAD_ERROR_BADREAD "File Read Error."
83 : #define LOAD_ERROR_READUNDERFLOW "File Read Error (underflow.)"
84 : #define LOAD_ERROR_NOPRINCIPALS "Failed to get principals."
85 : #define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad."
86 : #define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large"
87 :
88 2 : mozJSSubScriptLoader::mozJSSubScriptLoader()
89 : {
90 2 : }
91 :
92 0 : mozJSSubScriptLoader::~mozJSSubScriptLoader()
93 : {
94 0 : }
95 :
96 224 : NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader)
97 :
98 : static void
99 0 : ReportError(JSContext* cx, const nsACString& msg)
100 : {
101 0 : NS_ConvertUTF8toUTF16 ucMsg(msg);
102 :
103 0 : RootedValue exn(cx);
104 0 : if (xpc::NonVoidStringToJsval(cx, ucMsg, &exn)) {
105 0 : JS_SetPendingException(cx, exn);
106 : }
107 0 : }
108 :
109 : static void
110 0 : ReportError(JSContext* cx, const char* origMsg, nsIURI* uri)
111 : {
112 0 : if (!uri) {
113 0 : ReportError(cx, nsDependentCString(origMsg));
114 0 : return;
115 : }
116 :
117 0 : nsAutoCString spec;
118 0 : nsresult rv = uri->GetSpec(spec);
119 0 : if (NS_FAILED(rv))
120 0 : spec.Assign("(unknown)");
121 :
122 0 : nsAutoCString msg(origMsg);
123 0 : msg.Append(": ");
124 0 : msg.Append(spec);
125 0 : ReportError(cx, msg);
126 : }
127 :
128 : static bool
129 15 : PrepareScript(nsIURI* uri,
130 : JSContext* cx,
131 : HandleObject targetObj,
132 : const char* uriStr,
133 : const nsAString& charset,
134 : const char* buf,
135 : int64_t len,
136 : bool wantReturnValue,
137 : MutableHandleScript script)
138 : {
139 30 : JS::CompileOptions options(cx);
140 15 : options.setFileAndLine(uriStr, 1)
141 15 : .setVersion(JSVERSION_LATEST)
142 30 : .setNoScriptRval(!wantReturnValue);
143 15 : if (!charset.IsVoid()) {
144 0 : char16_t* scriptBuf = nullptr;
145 0 : size_t scriptLength = 0;
146 :
147 : nsresult rv =
148 0 : ScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len,
149 0 : charset, nullptr, scriptBuf, scriptLength);
150 :
151 : JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
152 0 : JS::SourceBufferHolder::GiveOwnership);
153 :
154 0 : if (NS_FAILED(rv)) {
155 0 : ReportError(cx, LOAD_ERROR_BADCHARSET, uri);
156 0 : return false;
157 : }
158 :
159 0 : if (JS_IsGlobalObject(targetObj)) {
160 0 : return JS::Compile(cx, options, srcBuf, script);
161 : }
162 0 : return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script);
163 : }
164 : // We only use lazy source when no special encoding is specified because
165 : // the lazy source loader doesn't know the encoding.
166 15 : options.setSourceIsLazy(true);
167 15 : if (JS_IsGlobalObject(targetObj)) {
168 14 : return JS::Compile(cx, options, buf, len, script);
169 : }
170 1 : return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
171 : }
172 :
173 : static bool
174 33 : EvalScript(JSContext* cx,
175 : HandleObject targetObj,
176 : MutableHandleValue retval,
177 : nsIURI* uri,
178 : bool startupCache,
179 : bool preloadCache,
180 : MutableHandleScript script)
181 : {
182 33 : if (JS_IsGlobalObject(targetObj)) {
183 15 : if (!JS::CloneAndExecuteScript(cx, script, retval)) {
184 0 : return false;
185 : }
186 : } else {
187 36 : JS::AutoObjectVector envChain(cx);
188 18 : if (!envChain.append(targetObj)) {
189 0 : return false;
190 : }
191 18 : if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
192 0 : return false;
193 : }
194 : }
195 :
196 66 : JSAutoCompartment rac(cx, targetObj);
197 33 : if (!JS_WrapValue(cx, retval)) {
198 0 : return false;
199 : }
200 :
201 33 : if (script && (startupCache || preloadCache)) {
202 66 : nsAutoCString cachePath;
203 33 : JSVersion version = JS_GetVersion(cx);
204 33 : cachePath.AppendPrintf("jssubloader/%d", version);
205 33 : PathifyURI(uri, cachePath);
206 :
207 66 : nsCString uriStr;
208 33 : if (preloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) {
209 : // Note that, when called during startup, this will keep the
210 : // original JSScript object alive for an indefinite amount of time.
211 : // This has the side-effect of keeping the global that the script
212 : // was compiled for alive, too.
213 : //
214 : // For most startups, the global in question will be the
215 : // CompilationScope, since we pre-compile any scripts that were
216 : // needed during the last startup in that scope. But for startups
217 : // when a non-cached script is used (e.g., after add-on
218 : // installation), this may be a Sandbox global, which may be
219 : // nuked but held alive by the JSScript.
220 : //
221 : // In general, this isn't a problem, since add-on Sandboxes which
222 : // use the script preloader are not destroyed until add-on shutdown,
223 : // and when add-ons are uninstalled or upgraded, the preloader cache
224 : // is immediately flushed after shutdown. But it's possible to
225 : // disable and reenable an add-on without uninstalling it, leading
226 : // to cached scripts being held alive, and tied to nuked Sandbox
227 : // globals. Given the unusual circumstances required to trigger
228 : // this, it's not a major concern. But it should be kept in mind.
229 33 : ScriptPreloader::GetSingleton().NoteScript(uriStr, cachePath, script);
230 : }
231 :
232 33 : if (startupCache) {
233 28 : JSAutoCompartment ac(cx, script);
234 14 : WriteCachedScript(StartupCache::GetSingleton(), cachePath, cx, script);
235 : }
236 : }
237 :
238 33 : return true;
239 : }
240 :
241 : class AsyncScriptLoader : public nsIIncrementalStreamLoaderObserver
242 : {
243 : public:
244 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
245 : NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
246 :
247 0 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader)
248 :
249 0 : AsyncScriptLoader(nsIChannel* aChannel, bool aWantReturnValue,
250 : JSObject* aTargetObj, const nsAString& aCharset,
251 : bool aCache, Promise* aPromise)
252 0 : : mChannel(aChannel)
253 : , mTargetObj(aTargetObj)
254 : , mPromise(aPromise)
255 : , mCharset(aCharset)
256 : , mWantReturnValue(aWantReturnValue)
257 0 : , mCache(aCache)
258 : {
259 : // Needed for the cycle collector to manage mTargetObj.
260 0 : mozilla::HoldJSObjects(this);
261 0 : }
262 :
263 : private:
264 0 : virtual ~AsyncScriptLoader() {
265 0 : mozilla::DropJSObjects(this);
266 0 : }
267 :
268 : RefPtr<nsIChannel> mChannel;
269 : Heap<JSObject*> mTargetObj;
270 : RefPtr<Promise> mPromise;
271 : nsString mCharset;
272 : bool mWantReturnValue;
273 : bool mCache;
274 : };
275 :
276 : NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader)
277 :
278 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader)
279 0 : NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver)
280 0 : NS_INTERFACE_MAP_END
281 :
282 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader)
283 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
284 0 : tmp->mTargetObj = nullptr;
285 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
286 :
287 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader)
288 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
289 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
290 :
291 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader)
292 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj)
293 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
294 :
295 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader)
296 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader)
297 :
298 : class MOZ_STACK_CLASS AutoRejectPromise
299 : {
300 : public:
301 0 : AutoRejectPromise(AutoEntryScript& aAutoEntryScript,
302 : Promise* aPromise,
303 : nsIGlobalObject* aGlobalObject)
304 0 : : mAutoEntryScript(aAutoEntryScript)
305 : , mPromise(aPromise)
306 0 : , mGlobalObject(aGlobalObject) {}
307 :
308 0 : ~AutoRejectPromise() {
309 0 : if (mPromise) {
310 0 : JSContext* cx = mAutoEntryScript.cx();
311 0 : RootedValue rejectionValue(cx, JS::UndefinedValue());
312 0 : if (mAutoEntryScript.HasException()) {
313 0 : Unused << mAutoEntryScript.PeekException(&rejectionValue);
314 : }
315 0 : mPromise->MaybeReject(cx, rejectionValue);
316 : }
317 0 : }
318 :
319 0 : void ResolvePromise(HandleValue aResolveValue) {
320 0 : mPromise->MaybeResolve(aResolveValue);
321 0 : mPromise = nullptr;
322 0 : }
323 :
324 : private:
325 : AutoEntryScript& mAutoEntryScript;
326 : RefPtr<Promise> mPromise;
327 : nsCOMPtr<nsIGlobalObject> mGlobalObject;
328 : };
329 :
330 : NS_IMETHODIMP
331 0 : AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
332 : nsISupports* aContext,
333 : uint32_t aDataLength,
334 : const uint8_t* aData,
335 : uint32_t *aConsumedData)
336 : {
337 0 : return NS_OK;
338 : }
339 :
340 : NS_IMETHODIMP
341 0 : AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
342 : nsISupports* aContext,
343 : nsresult aStatus,
344 : uint32_t aLength,
345 : const uint8_t* aBuf)
346 : {
347 0 : nsCOMPtr<nsIURI> uri;
348 0 : mChannel->GetURI(getter_AddRefs(uri));
349 :
350 0 : nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(mTargetObj);
351 0 : AutoEntryScript aes(globalObject, "async loadSubScript");
352 0 : AutoRejectPromise autoPromise(aes, mPromise, globalObject);
353 0 : JSContext* cx = aes.cx();
354 :
355 0 : if (NS_FAILED(aStatus)) {
356 0 : ReportError(cx, "Unable to load script.", uri);
357 : }
358 : // Just notify that we are done with this load.
359 0 : NS_ENSURE_SUCCESS(aStatus, NS_OK);
360 :
361 0 : if (aLength == 0) {
362 0 : ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
363 0 : return NS_OK;
364 : }
365 :
366 0 : if (aLength > INT32_MAX) {
367 0 : ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
368 0 : return NS_OK;
369 : }
370 :
371 0 : RootedScript script(cx);
372 0 : nsAutoCString spec;
373 0 : nsresult rv = uri->GetSpec(spec);
374 0 : NS_ENSURE_SUCCESS(rv, rv);
375 :
376 0 : RootedObject targetObj(cx, mTargetObj);
377 :
378 0 : if (!PrepareScript(uri, cx, targetObj, spec.get(), mCharset,
379 : reinterpret_cast<const char*>(aBuf), aLength,
380 0 : mWantReturnValue, &script))
381 : {
382 0 : return NS_OK;
383 : }
384 :
385 0 : JS::Rooted<JS::Value> retval(cx);
386 0 : if (EvalScript(cx, targetObj, &retval, uri, mCache,
387 0 : mCache && !mWantReturnValue,
388 : &script)) {
389 0 : autoPromise.ResolvePromise(retval);
390 : }
391 :
392 0 : return NS_OK;
393 : }
394 :
395 : nsresult
396 0 : mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri,
397 : HandleObject targetObj,
398 : const nsAString& charset,
399 : nsIIOService* serv,
400 : bool wantReturnValue,
401 : bool cache,
402 : MutableHandleValue retval)
403 : {
404 0 : nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(targetObj);
405 0 : ErrorResult result;
406 :
407 0 : AutoJSAPI jsapi;
408 0 : if (NS_WARN_IF(!jsapi.Init(globalObject))) {
409 0 : return NS_ERROR_UNEXPECTED;
410 : }
411 :
412 0 : RefPtr<Promise> promise = Promise::Create(globalObject, result);
413 0 : if (result.Failed()) {
414 0 : return result.StealNSResult();
415 : }
416 :
417 0 : DebugOnly<bool> asJS = ToJSValue(jsapi.cx(), promise, retval);
418 0 : MOZ_ASSERT(asJS, "Should not fail to convert the promise to a JS value");
419 :
420 : // We create a channel and call SetContentType, to avoid expensive MIME type
421 : // lookups (bug 632490).
422 0 : nsCOMPtr<nsIChannel> channel;
423 : nsresult rv;
424 0 : rv = NS_NewChannel(getter_AddRefs(channel),
425 : uri,
426 : nsContentUtils::GetSystemPrincipal(),
427 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
428 : nsIContentPolicy::TYPE_OTHER,
429 : nullptr, // aLoadGroup
430 : nullptr, // aCallbacks
431 : nsIRequest::LOAD_NORMAL,
432 0 : serv);
433 :
434 0 : if (!NS_SUCCEEDED(rv)) {
435 0 : return rv;
436 : }
437 :
438 0 : channel->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
439 :
440 : RefPtr<AsyncScriptLoader> loadObserver =
441 : new AsyncScriptLoader(channel,
442 : wantReturnValue,
443 : targetObj,
444 : charset,
445 : cache,
446 0 : promise);
447 :
448 0 : nsCOMPtr<nsIIncrementalStreamLoader> loader;
449 0 : rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
450 0 : NS_ENSURE_SUCCESS(rv, rv);
451 :
452 0 : nsCOMPtr<nsIStreamListener> listener = loader.get();
453 0 : return channel->AsyncOpen2(listener);
454 : }
455 :
456 : bool
457 15 : mozJSSubScriptLoader::ReadScript(nsIURI* uri,
458 : JSContext* cx,
459 : HandleObject targetObj,
460 : const nsAString& charset,
461 : const char* uriStr,
462 : nsIIOService* serv,
463 : bool wantReturnValue,
464 : MutableHandleScript script)
465 : {
466 15 : script.set(nullptr);
467 :
468 : // We create a channel and call SetContentType, to avoid expensive MIME type
469 : // lookups (bug 632490).
470 30 : nsCOMPtr<nsIChannel> chan;
471 30 : nsCOMPtr<nsIInputStream> instream;
472 : nsresult rv;
473 30 : rv = NS_NewChannel(getter_AddRefs(chan),
474 : uri,
475 : nsContentUtils::GetSystemPrincipal(),
476 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
477 : nsIContentPolicy::TYPE_OTHER,
478 : nullptr, // aLoadGroup
479 : nullptr, // aCallbacks
480 : nsIRequest::LOAD_NORMAL,
481 15 : serv);
482 :
483 15 : if (NS_SUCCEEDED(rv)) {
484 15 : chan->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
485 15 : rv = chan->Open2(getter_AddRefs(instream));
486 : }
487 :
488 15 : if (NS_FAILED(rv)) {
489 0 : ReportError(cx, LOAD_ERROR_NOSTREAM, uri);
490 0 : return false;
491 : }
492 :
493 15 : int64_t len = -1;
494 :
495 15 : rv = chan->GetContentLength(&len);
496 15 : if (NS_FAILED(rv) || len == -1) {
497 0 : ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
498 0 : return false;
499 : }
500 :
501 15 : if (len > INT32_MAX) {
502 0 : ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
503 0 : return false;
504 : }
505 :
506 30 : nsCString buf;
507 15 : rv = NS_ReadInputStreamToString(instream, buf, len);
508 15 : NS_ENSURE_SUCCESS(rv, false);
509 :
510 15 : return PrepareScript(uri, cx, targetObj, uriStr, charset,
511 : buf.get(), len, wantReturnValue,
512 15 : script);
513 : }
514 :
515 : NS_IMETHODIMP
516 33 : mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
517 : HandleValue target,
518 : const nsAString& charset,
519 : JSContext* cx,
520 : MutableHandleValue retval)
521 : {
522 : /*
523 : * Loads a local url and evals it into the current cx
524 : * Synchronous (an async version would be cool too.)
525 : * url: The url to load. Must be local so that it can be loaded
526 : * synchronously.
527 : * targetObj: Optional object to eval the script onto (defaults to context
528 : * global)
529 : * charset: Optional character set to use for reading
530 : * returns: Whatever jsval the script pointed to by the url returns.
531 : * Should ONLY (O N L Y !) be called from JavaScript code.
532 : */
533 66 : LoadSubScriptOptions options(cx);
534 33 : options.charset = charset;
535 33 : options.target = target.isObject() ? &target.toObject() : nullptr;
536 66 : return DoLoadSubScriptWithOptions(url, options, cx, retval);
537 : }
538 :
539 :
540 : NS_IMETHODIMP
541 0 : mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url,
542 : HandleValue optionsVal,
543 : JSContext* cx,
544 : MutableHandleValue retval)
545 : {
546 0 : if (!optionsVal.isObject())
547 0 : return NS_ERROR_INVALID_ARG;
548 0 : LoadSubScriptOptions options(cx, &optionsVal.toObject());
549 0 : if (!options.Parse())
550 0 : return NS_ERROR_INVALID_ARG;
551 0 : return DoLoadSubScriptWithOptions(url, options, cx, retval);
552 : }
553 :
554 : nsresult
555 33 : mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
556 : LoadSubScriptOptions& options,
557 : JSContext* cx,
558 : MutableHandleValue retval)
559 : {
560 33 : nsresult rv = NS_OK;
561 66 : RootedObject targetObj(cx);
562 33 : if (options.target) {
563 33 : targetObj = options.target;
564 : } else {
565 0 : mozJSComponentLoader* loader = mozJSComponentLoader::Get();
566 0 : loader->FindTargetObject(cx, &targetObj);
567 0 : MOZ_ASSERT(JS_IsGlobalObject(targetObj));
568 : }
569 :
570 33 : targetObj = JS_FindCompilationScope(cx, targetObj);
571 33 : if (!targetObj)
572 0 : return NS_ERROR_FAILURE;
573 :
574 : /* load up the url. From here on, failures are reflected as ``custom''
575 : * js exceptions */
576 66 : nsCOMPtr<nsIURI> uri;
577 66 : nsAutoCString uriStr;
578 66 : nsAutoCString scheme;
579 :
580 : // Figure out who's calling us
581 66 : JS::AutoFilename filename;
582 33 : if (!JS::DescribeScriptedCaller(cx, &filename)) {
583 : // No scripted frame means we don't know who's calling, bail.
584 0 : return NS_ERROR_FAILURE;
585 : }
586 :
587 66 : JSAutoCompartment ac(cx, targetObj);
588 :
589 66 : nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
590 33 : if (!serv) {
591 0 : ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSERVICE));
592 0 : return NS_OK;
593 : }
594 :
595 66 : const nsCString& asciiUrl = NS_LossyConvertUTF16toASCII(url);
596 66 : AUTO_PROFILER_LABEL_DYNAMIC(
597 : "mozJSSubScriptLoader::DoLoadSubScriptWithOptions", OTHER,
598 : asciiUrl.get());
599 :
600 : // Make sure to explicitly create the URI, since we'll need the
601 : // canonicalized spec.
602 33 : rv = NS_NewURI(getter_AddRefs(uri), asciiUrl.get(), nullptr, serv);
603 33 : if (NS_FAILED(rv)) {
604 0 : ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOURI));
605 0 : return NS_OK;
606 : }
607 :
608 33 : rv = uri->GetSpec(uriStr);
609 33 : if (NS_FAILED(rv)) {
610 0 : ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSPEC));
611 0 : return NS_OK;
612 : }
613 :
614 33 : rv = uri->GetScheme(scheme);
615 33 : if (NS_FAILED(rv)) {
616 0 : ReportError(cx, LOAD_ERROR_NOSCHEME, uri);
617 0 : return NS_OK;
618 : }
619 :
620 66 : if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app") &&
621 33 : !scheme.EqualsLiteral("blob")) {
622 : // This might be a URI to a local file, though!
623 66 : nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
624 66 : nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
625 33 : if (!fileURL) {
626 0 : ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL, uri);
627 0 : return NS_OK;
628 : }
629 :
630 : // For file URIs prepend the filename with the filename of the
631 : // calling script, and " -> ". See bug 418356.
632 66 : nsAutoCString tmp(filename.get());
633 33 : tmp.AppendLiteral(" -> ");
634 33 : tmp.Append(uriStr);
635 :
636 33 : uriStr = tmp;
637 : }
638 :
639 : // Suppress caching if we're compiling as content or if we're loading a
640 : // blob: URI.
641 33 : bool ignoreCache = options.ignoreCache
642 33 : || !GetObjectPrincipal(targetObj)->GetIsSystemPrincipal()
643 66 : || scheme.EqualsLiteral("blob");
644 33 : StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton();
645 :
646 33 : JSVersion version = JS_GetVersion(cx);
647 66 : nsAutoCString cachePath;
648 33 : cachePath.AppendPrintf("jssubloader/%d", version);
649 33 : PathifyURI(uri, cachePath);
650 :
651 66 : RootedScript script(cx);
652 33 : if (!options.ignoreCache) {
653 33 : if (!options.wantReturnValue)
654 33 : script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
655 33 : if (!script && cache)
656 14 : rv = ReadCachedScript(cache, cachePath, cx, &script);
657 33 : if (NS_FAILED(rv) || !script) {
658 : // ReadCachedScript may have set a pending exception.
659 15 : JS_ClearPendingException(cx);
660 : }
661 : }
662 :
663 : // If we are doing an async load, trigger it and bail out.
664 33 : if (!script && options.async) {
665 0 : return ReadScriptAsync(uri, targetObj, options.charset, serv,
666 0 : options.wantReturnValue, !!cache, retval);
667 : }
668 :
669 33 : if (script) {
670 : // |script| came from the cache, so don't bother writing it
671 : // |back there.
672 18 : cache = nullptr;
673 45 : } else if (!ReadScript(uri, cx, targetObj, options.charset,
674 15 : static_cast<const char*>(uriStr.get()), serv,
675 15 : options.wantReturnValue, &script)) {
676 0 : return NS_OK;
677 : }
678 :
679 66 : Unused << EvalScript(cx, targetObj, retval, uri, !!cache,
680 33 : !ignoreCache && !options.wantReturnValue,
681 : &script);
682 33 : return NS_OK;
683 : }
684 :
685 : /**
686 : * Let us compile scripts from a URI off the main thread.
687 : */
688 :
689 : class ScriptPrecompiler : public nsIIncrementalStreamLoaderObserver
690 : {
691 : public:
692 : NS_DECL_ISUPPORTS
693 : NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
694 :
695 0 : ScriptPrecompiler(nsIObserver* aObserver,
696 : nsIPrincipal* aPrincipal,
697 : nsIChannel* aChannel)
698 0 : : mObserver(aObserver)
699 : , mPrincipal(aPrincipal)
700 : , mChannel(aChannel)
701 : , mScriptBuf(nullptr)
702 0 : , mScriptLength(0)
703 0 : {}
704 :
705 : static void OffThreadCallback(void* aToken, void* aData);
706 :
707 : /* Sends the "done" notification back. Main thread only. */
708 : void SendObserverNotification();
709 :
710 : private:
711 0 : virtual ~ScriptPrecompiler()
712 0 : {
713 0 : if (mScriptBuf) {
714 0 : js_free(mScriptBuf);
715 : }
716 0 : }
717 :
718 : RefPtr<nsIObserver> mObserver;
719 : RefPtr<nsIPrincipal> mPrincipal;
720 : RefPtr<nsIChannel> mChannel;
721 : char16_t* mScriptBuf;
722 : size_t mScriptLength;
723 : };
724 :
725 0 : NS_IMPL_ISUPPORTS(ScriptPrecompiler, nsIIncrementalStreamLoaderObserver);
726 :
727 0 : class NotifyPrecompilationCompleteRunnable : public Runnable
728 : {
729 : public:
730 : NS_DECL_NSIRUNNABLE
731 :
732 0 : explicit NotifyPrecompilationCompleteRunnable(
733 : ScriptPrecompiler* aPrecompiler)
734 0 : : mozilla::Runnable("NotifyPrecompilationCompleteRunnable")
735 : , mPrecompiler(aPrecompiler)
736 0 : , mToken(nullptr)
737 0 : {}
738 :
739 0 : void SetToken(void* aToken) {
740 0 : MOZ_ASSERT(aToken && !mToken);
741 0 : mToken = aToken;
742 0 : }
743 :
744 : protected:
745 : RefPtr<ScriptPrecompiler> mPrecompiler;
746 : void* mToken;
747 : };
748 :
749 : /* RAII helper class to send observer notifications */
750 : class AutoSendObserverNotification {
751 : public:
752 0 : explicit AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler)
753 0 : : mPrecompiler(aPrecompiler)
754 0 : {}
755 :
756 0 : ~AutoSendObserverNotification() {
757 0 : if (mPrecompiler) {
758 0 : mPrecompiler->SendObserverNotification();
759 : }
760 0 : }
761 :
762 0 : void Disarm() {
763 0 : mPrecompiler = nullptr;
764 0 : }
765 :
766 : private:
767 : ScriptPrecompiler* mPrecompiler;
768 : };
769 :
770 : NS_IMETHODIMP
771 0 : NotifyPrecompilationCompleteRunnable::Run(void)
772 : {
773 0 : MOZ_ASSERT(NS_IsMainThread());
774 0 : MOZ_ASSERT(mPrecompiler);
775 :
776 0 : AutoSendObserverNotification notifier(mPrecompiler);
777 :
778 0 : if (mToken) {
779 0 : JSContext* cx = XPCJSContext::Get()->Context();
780 0 : NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
781 0 : JS::CancelOffThreadScript(cx, mToken);
782 : }
783 :
784 0 : return NS_OK;
785 : }
786 :
787 : NS_IMETHODIMP
788 0 : ScriptPrecompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
789 : nsISupports* aContext,
790 : uint32_t aDataLength,
791 : const uint8_t* aData,
792 : uint32_t *aConsumedData)
793 : {
794 0 : return NS_OK;
795 : }
796 :
797 : NS_IMETHODIMP
798 0 : ScriptPrecompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
799 : nsISupports* aContext,
800 : nsresult aStatus,
801 : uint32_t aLength,
802 : const uint8_t* aString)
803 : {
804 0 : AutoSendObserverNotification notifier(this);
805 :
806 : // Just notify that we are done with this load.
807 0 : NS_ENSURE_SUCCESS(aStatus, NS_OK);
808 :
809 : // Convert data to char16_t* and prepare to call CompileOffThread.
810 0 : nsAutoString hintCharset;
811 : nsresult rv =
812 0 : ScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
813 : hintCharset, nullptr,
814 0 : mScriptBuf, mScriptLength);
815 :
816 0 : NS_ENSURE_SUCCESS(rv, NS_OK);
817 :
818 : // Our goal is to cache persistently the compiled script and to avoid quota
819 : // checks. Since the caching mechanism decide the persistence type based on
820 : // the principal, we create a new global with the app's principal.
821 : // We then enter its compartment to compile with its principal.
822 0 : AutoSafeJSContext cx;
823 0 : RootedValue v(cx);
824 0 : SandboxOptions sandboxOptions;
825 0 : sandboxOptions.sandboxName.AssignASCII("asm.js precompilation");
826 0 : sandboxOptions.invisibleToDebugger = true;
827 0 : sandboxOptions.discardSource = true;
828 0 : rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions);
829 0 : NS_ENSURE_SUCCESS(rv, NS_OK);
830 :
831 0 : JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject()));
832 :
833 0 : JS::CompileOptions options(cx, JSVERSION_DEFAULT);
834 0 : options.forceAsync = true;
835 :
836 0 : nsCOMPtr<nsIURI> uri;
837 0 : mChannel->GetURI(getter_AddRefs(uri));
838 0 : nsAutoCString spec;
839 0 : uri->GetSpec(spec);
840 0 : options.setFile(spec.get());
841 :
842 0 : if (!JS::CanCompileOffThread(cx, options, mScriptLength)) {
843 0 : NS_WARNING("Can't compile script off thread!");
844 0 : return NS_OK;
845 : }
846 :
847 : RefPtr<NotifyPrecompilationCompleteRunnable> runnable =
848 0 : new NotifyPrecompilationCompleteRunnable(this);
849 :
850 0 : if (!JS::CompileOffThread(cx, options,
851 0 : mScriptBuf, mScriptLength,
852 : OffThreadCallback,
853 0 : static_cast<void*>(runnable))) {
854 0 : NS_WARNING("Failed to compile script off thread!");
855 0 : return NS_OK;
856 : }
857 :
858 0 : Unused << runnable.forget();
859 0 : notifier.Disarm();
860 :
861 0 : return NS_OK;
862 : }
863 :
864 : /* static */
865 : void
866 0 : ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData)
867 : {
868 : RefPtr<NotifyPrecompilationCompleteRunnable> runnable =
869 0 : dont_AddRef(static_cast<NotifyPrecompilationCompleteRunnable*>(aData));
870 0 : runnable->SetToken(aToken);
871 :
872 0 : NS_DispatchToMainThread(runnable);
873 0 : }
874 :
875 : void
876 0 : ScriptPrecompiler::SendObserverNotification()
877 : {
878 0 : MOZ_ASSERT(mChannel && mObserver);
879 0 : MOZ_ASSERT(NS_IsMainThread());
880 :
881 0 : nsCOMPtr<nsIURI> uri;
882 0 : mChannel->GetURI(getter_AddRefs(uri));
883 0 : mObserver->Observe(uri, "script-precompiled", nullptr);
884 0 : }
885 :
886 : NS_IMETHODIMP
887 0 : mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI,
888 : nsIPrincipal* aPrincipal,
889 : nsIObserver* aObserver)
890 : {
891 0 : nsCOMPtr<nsIChannel> channel;
892 0 : nsresult rv = NS_NewChannel(getter_AddRefs(channel),
893 : aURI,
894 : nsContentUtils::GetSystemPrincipal(),
895 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
896 0 : nsIContentPolicy::TYPE_OTHER);
897 :
898 0 : NS_ENSURE_SUCCESS(rv, rv);
899 :
900 : RefPtr<ScriptPrecompiler> loadObserver =
901 0 : new ScriptPrecompiler(aObserver, aPrincipal, channel);
902 :
903 0 : nsCOMPtr<nsIIncrementalStreamLoader> loader;
904 0 : rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
905 0 : NS_ENSURE_SUCCESS(rv, rv);
906 :
907 0 : nsCOMPtr<nsIStreamListener> listener = loader.get();
908 0 : rv = channel->AsyncOpen2(listener);
909 0 : NS_ENSURE_SUCCESS(rv, rv);
910 :
911 0 : return NS_OK;
912 : }
|