Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
7 #include "mozJSSubScriptLoader.h"
8 #include "mozJSComponentLoader.h"
9 #include "mozJSLoaderUtils.h"
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 "nsScriptLoader.h"
19 #include "nsIScriptSecurityManager.h"
20 #include "nsThreadUtils.h"
22 #include "jsapi.h"
23 #include "jsfriendapi.h"
24 #include "js/OldDebugAPI.h"
25 #include "nsJSPrincipals.h"
26 #include "xpcpublic.h" // For xpc::SystemErrorReporter
27 #include "xpcprivate.h" // For xpc::OptionsBase
28 #include "jswrapper.h"
30 #include "mozilla/scache/StartupCache.h"
31 #include "mozilla/scache/StartupCacheUtils.h"
32 #include "mozilla/unused.h"
34 using namespace mozilla::scache;
35 using namespace JS;
36 using namespace xpc;
37 using namespace mozilla;
39 class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase {
40 public:
41 LoadSubScriptOptions(JSContext *cx = xpc_GetSafeJSContext(),
42 JSObject *options = nullptr)
43 : OptionsBase(cx, options)
44 , target(cx)
45 , charset(NullString())
46 , ignoreCache(false)
47 { }
49 virtual bool Parse() {
50 return ParseObject("target", &target) &&
51 ParseString("charset", charset) &&
52 ParseBoolean("ignoreCache", &ignoreCache);
53 }
55 RootedObject target;
56 nsString charset;
57 bool ignoreCache;
58 };
61 /* load() error msgs, XXX localize? */
62 #define LOAD_ERROR_NOSERVICE "Error creating IO Service."
63 #define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)"
64 #define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad."
65 #define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI."
66 #define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)"
67 #define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)"
68 #define LOAD_ERROR_BADCHARSET "Error converting to specified charset"
69 #define LOAD_ERROR_BADREAD "File Read Error."
70 #define LOAD_ERROR_READUNDERFLOW "File Read Error (underflow.)"
71 #define LOAD_ERROR_NOPRINCIPALS "Failed to get principals."
72 #define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad."
73 #define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large"
75 mozJSSubScriptLoader::mozJSSubScriptLoader() : mSystemPrincipal(nullptr)
76 {
77 // Force construction of the JS component loader. We may need it later.
78 nsCOMPtr<xpcIJSModuleLoader> componentLoader =
79 do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID);
80 }
82 mozJSSubScriptLoader::~mozJSSubScriptLoader()
83 {
84 /* empty */
85 }
87 NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader)
89 static nsresult
90 ReportError(JSContext *cx, const char *msg)
91 {
92 RootedValue exn(cx, JS::StringValue(JS_NewStringCopyZ(cx, msg)));
93 JS_SetPendingException(cx, exn);
94 return NS_OK;
95 }
97 nsresult
98 mozJSSubScriptLoader::ReadScript(nsIURI *uri, JSContext *cx, JSObject *targetObjArg,
99 const nsAString &charset, const char *uriStr,
100 nsIIOService *serv, nsIPrincipal *principal,
101 bool reuseGlobal, JS::MutableHandleScript script,
102 JS::MutableHandleFunction function)
103 {
104 RootedObject target_obj(cx, targetObjArg);
106 script.set(nullptr);
107 function.set(nullptr);
109 // Instead of calling NS_OpenURI, we create the channel ourselves and call
110 // SetContentType, to avoid expensive MIME type lookups (bug 632490).
111 nsCOMPtr<nsIChannel> chan;
112 nsCOMPtr<nsIInputStream> instream;
113 nsresult rv = NS_NewChannel(getter_AddRefs(chan), uri, serv,
114 nullptr, nullptr, nsIRequest::LOAD_NORMAL);
115 if (NS_SUCCEEDED(rv)) {
116 chan->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
117 rv = chan->Open(getter_AddRefs(instream));
118 }
120 if (NS_FAILED(rv)) {
121 return ReportError(cx, LOAD_ERROR_NOSTREAM);
122 }
124 int64_t len = -1;
126 rv = chan->GetContentLength(&len);
127 if (NS_FAILED(rv) || len == -1) {
128 return ReportError(cx, LOAD_ERROR_NOCONTENT);
129 }
131 if (len > INT32_MAX) {
132 return ReportError(cx, LOAD_ERROR_CONTENTTOOBIG);
133 }
135 nsCString buf;
136 rv = NS_ReadInputStreamToString(instream, buf, len);
137 if (NS_FAILED(rv))
138 return rv;
140 /* set our own error reporter so we can report any bad things as catchable
141 * exceptions, including the source/line number */
142 JSErrorReporter er = JS_SetErrorReporter(cx, xpc::SystemErrorReporter);
144 JS::CompileOptions options(cx);
145 options.setFileAndLine(uriStr, 1);
146 if (!charset.IsVoid()) {
147 jschar *scriptBuf = nullptr;
148 size_t scriptLength = 0;
150 rv = nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf.get()), len,
151 charset, nullptr, scriptBuf, scriptLength);
153 JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
154 JS::SourceBufferHolder::GiveOwnership);
156 if (NS_FAILED(rv)) {
157 return ReportError(cx, LOAD_ERROR_BADCHARSET);
158 }
160 if (!reuseGlobal) {
161 JS::Compile(cx, target_obj, options, srcBuf, script);
162 } else {
163 JS::CompileFunction(cx, target_obj, options,
164 nullptr, 0, nullptr,
165 srcBuf,
166 function);
167 }
168 } else {
169 // We only use lazy source when no special encoding is specified because
170 // the lazy source loader doesn't know the encoding.
171 if (!reuseGlobal) {
172 options.setSourceIsLazy(true);
173 script.set(JS::Compile(cx, target_obj, options, buf.get(), len));
174 } else {
175 function.set(JS::CompileFunction(cx, target_obj, options,
176 nullptr, 0, nullptr, buf.get(),
177 len));
178 }
179 }
181 /* repent for our evil deeds */
182 JS_SetErrorReporter(cx, er);
184 return NS_OK;
185 }
187 NS_IMETHODIMP
188 mozJSSubScriptLoader::LoadSubScript(const nsAString &url,
189 HandleValue target,
190 const nsAString &charset,
191 JSContext *cx,
192 MutableHandleValue retval)
193 {
194 /*
195 * Loads a local url and evals it into the current cx
196 * Synchronous (an async version would be cool too.)
197 * url: The url to load. Must be local so that it can be loaded
198 * synchronously.
199 * target_obj: Optional object to eval the script onto (defaults to context
200 * global)
201 * charset: Optional character set to use for reading
202 * returns: Whatever jsval the script pointed to by the url returns.
203 * Should ONLY (O N L Y !) be called from JavaScript code.
204 */
205 LoadSubScriptOptions options(cx);
206 options.charset = charset;
207 options.target = target.isObject() ? &target.toObject() : nullptr;
208 return DoLoadSubScriptWithOptions(url, options, cx, retval);
209 }
212 NS_IMETHODIMP
213 mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString &url,
214 HandleValue optionsVal,
215 JSContext *cx,
216 MutableHandleValue retval)
217 {
218 if (!optionsVal.isObject())
219 return NS_ERROR_INVALID_ARG;
220 LoadSubScriptOptions options(cx, &optionsVal.toObject());
221 if (!options.Parse())
222 return NS_ERROR_INVALID_ARG;
223 return DoLoadSubScriptWithOptions(url, options, cx, retval);
224 }
226 nsresult
227 mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString &url,
228 LoadSubScriptOptions &options,
229 JSContext *cx,
230 MutableHandleValue retval)
231 {
232 nsresult rv = NS_OK;
234 /* set the system principal if it's not here already */
235 if (!mSystemPrincipal) {
236 nsCOMPtr<nsIScriptSecurityManager> secman =
237 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
238 if (!secman)
239 return NS_OK;
241 rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal));
242 if (NS_FAILED(rv) || !mSystemPrincipal)
243 return rv;
244 }
246 RootedObject targetObj(cx);
247 mozJSComponentLoader *loader = mozJSComponentLoader::Get();
248 rv = loader->FindTargetObject(cx, &targetObj);
249 NS_ENSURE_SUCCESS(rv, rv);
251 // We base reusingGlobal off of what the loader told us, but we may not
252 // actually be using that object.
253 bool reusingGlobal = !JS_IsGlobalObject(targetObj);
255 if (options.target)
256 targetObj = options.target;
258 // Remember an object out of the calling compartment so that we
259 // can properly wrap the result later.
260 nsCOMPtr<nsIPrincipal> principal = mSystemPrincipal;
261 RootedObject result_obj(cx, targetObj);
262 targetObj = JS_FindCompilationScope(cx, targetObj);
263 if (!targetObj)
264 return NS_ERROR_FAILURE;
266 if (targetObj != result_obj)
267 principal = GetObjectPrincipal(targetObj);
269 JSAutoCompartment ac(cx, targetObj);
271 /* load up the url. From here on, failures are reflected as ``custom''
272 * js exceptions */
273 nsCOMPtr<nsIURI> uri;
274 nsAutoCString uriStr;
275 nsAutoCString scheme;
277 // Figure out who's calling us
278 JS::AutoFilename filename;
279 if (!JS::DescribeScriptedCaller(cx, &filename)) {
280 // No scripted frame means we don't know who's calling, bail.
281 return NS_ERROR_FAILURE;
282 }
284 // Suppress caching if we're compiling as content.
285 StartupCache* cache = (principal == mSystemPrincipal)
286 ? StartupCache::GetSingleton()
287 : nullptr;
288 nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
289 if (!serv) {
290 return ReportError(cx, LOAD_ERROR_NOSERVICE);
291 }
293 // Make sure to explicitly create the URI, since we'll need the
294 // canonicalized spec.
295 rv = NS_NewURI(getter_AddRefs(uri), NS_LossyConvertUTF16toASCII(url).get(), nullptr, serv);
296 if (NS_FAILED(rv)) {
297 return ReportError(cx, LOAD_ERROR_NOURI);
298 }
300 rv = uri->GetSpec(uriStr);
301 if (NS_FAILED(rv)) {
302 return ReportError(cx, LOAD_ERROR_NOSPEC);
303 }
305 rv = uri->GetScheme(scheme);
306 if (NS_FAILED(rv)) {
307 return ReportError(cx, LOAD_ERROR_NOSCHEME);
308 }
310 if (!scheme.EqualsLiteral("chrome")) {
311 // This might be a URI to a local file, though!
312 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
313 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
314 if (!fileURL) {
315 return ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL);
316 }
318 // For file URIs prepend the filename with the filename of the
319 // calling script, and " -> ". See bug 418356.
320 nsAutoCString tmp(filename.get());
321 tmp.AppendLiteral(" -> ");
322 tmp.Append(uriStr);
324 uriStr = tmp;
325 }
327 bool writeScript = false;
328 JSVersion version = JS_GetVersion(cx);
329 nsAutoCString cachePath;
330 cachePath.AppendPrintf("jssubloader/%d", version);
331 PathifyURI(uri, cachePath);
333 RootedFunction function(cx);
334 RootedScript script(cx);
335 if (cache && !options.ignoreCache)
336 rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
337 if (!script) {
338 rv = ReadScript(uri, cx, targetObj, options.charset,
339 static_cast<const char*>(uriStr.get()), serv,
340 principal, reusingGlobal, &script, &function);
341 writeScript = !!script;
342 }
344 if (NS_FAILED(rv) || (!script && !function))
345 return rv;
347 if (function) {
348 script = JS_GetFunctionScript(cx, function);
349 }
351 loader->NoteSubScript(script, targetObj);
354 bool ok = false;
355 if (function) {
356 ok = JS_CallFunction(cx, targetObj, function, JS::HandleValueArray::empty(),
357 retval);
358 } else {
359 ok = JS_ExecuteScriptVersion(cx, targetObj, script, retval, version);
360 }
362 if (ok) {
363 JSAutoCompartment rac(cx, result_obj);
364 if (!JS_WrapValue(cx, retval))
365 return NS_ERROR_UNEXPECTED;
366 }
368 if (cache && ok && writeScript) {
369 WriteCachedScript(cache, cachePath, cx, mSystemPrincipal, script);
370 }
372 return NS_OK;
373 }
375 /**
376 * Let us compile scripts from a URI off the main thread.
377 */
379 class ScriptPrecompiler : public nsIStreamLoaderObserver
380 {
381 public:
382 NS_DECL_ISUPPORTS
383 NS_DECL_NSISTREAMLOADEROBSERVER
385 ScriptPrecompiler(nsIObserver* aObserver,
386 nsIPrincipal* aPrincipal,
387 nsIChannel* aChannel)
388 : mObserver(aObserver)
389 , mPrincipal(aPrincipal)
390 , mChannel(aChannel)
391 , mScriptBuf(nullptr)
392 , mScriptLength(0)
393 {}
395 virtual ~ScriptPrecompiler()
396 {
397 if (mScriptBuf) {
398 js_free(mScriptBuf);
399 }
400 }
402 static void OffThreadCallback(void *aToken, void *aData);
404 /* Sends the "done" notification back. Main thread only. */
405 void SendObserverNotification();
407 private:
408 nsRefPtr<nsIObserver> mObserver;
409 nsRefPtr<nsIPrincipal> mPrincipal;
410 nsRefPtr<nsIChannel> mChannel;
411 jschar* mScriptBuf;
412 size_t mScriptLength;
413 };
415 NS_IMPL_ISUPPORTS(ScriptPrecompiler, nsIStreamLoaderObserver);
417 class NotifyPrecompilationCompleteRunnable : public nsRunnable
418 {
419 public:
420 NS_DECL_NSIRUNNABLE
422 NotifyPrecompilationCompleteRunnable(ScriptPrecompiler* aPrecompiler)
423 : mPrecompiler(aPrecompiler)
424 , mToken(nullptr)
425 {}
427 void SetToken(void* aToken) {
428 MOZ_ASSERT(aToken && !mToken);
429 mToken = aToken;
430 }
432 protected:
433 nsRefPtr<ScriptPrecompiler> mPrecompiler;
434 void* mToken;
435 };
437 /* RAII helper class to send observer notifications */
438 class AutoSendObserverNotification {
439 public:
440 AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler)
441 : mPrecompiler(aPrecompiler)
442 {}
444 ~AutoSendObserverNotification() {
445 if (mPrecompiler) {
446 mPrecompiler->SendObserverNotification();
447 }
448 }
450 void Disarm() {
451 mPrecompiler = nullptr;
452 }
454 private:
455 ScriptPrecompiler* mPrecompiler;
456 };
458 NS_IMETHODIMP
459 NotifyPrecompilationCompleteRunnable::Run(void)
460 {
461 MOZ_ASSERT(NS_IsMainThread());
462 MOZ_ASSERT(mPrecompiler);
464 AutoSendObserverNotification notifier(mPrecompiler);
466 if (mToken) {
467 JSRuntime *rt = XPCJSRuntime::Get()->Runtime();
468 NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
469 JS::FinishOffThreadScript(nullptr, rt, mToken);
470 }
472 return NS_OK;
473 }
475 NS_IMETHODIMP
476 ScriptPrecompiler::OnStreamComplete(nsIStreamLoader* aLoader,
477 nsISupports* aContext,
478 nsresult aStatus,
479 uint32_t aLength,
480 const uint8_t* aString)
481 {
482 AutoSendObserverNotification notifier(this);
484 // Just notify that we are done with this load.
485 NS_ENSURE_SUCCESS(aStatus, NS_OK);
487 // Convert data to jschar* and prepare to call CompileOffThread.
488 nsAutoString hintCharset;
489 nsresult rv =
490 nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
491 hintCharset, nullptr,
492 mScriptBuf, mScriptLength);
494 NS_ENSURE_SUCCESS(rv, NS_OK);
496 // Our goal is to cache persistently the compiled script and to avoid quota
497 // checks. Since the caching mechanism decide the persistence type based on
498 // the principal, we create a new global with the app's principal.
499 // We then enter its compartment to compile with its principal.
500 AutoSafeJSContext cx;
501 RootedValue v(cx);
502 SandboxOptions sandboxOptions;
503 sandboxOptions.sandboxName.AssignASCII("asm.js precompilation");
504 sandboxOptions.invisibleToDebugger = true;
505 sandboxOptions.discardSource = true;
506 rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions);
507 NS_ENSURE_SUCCESS(rv, NS_OK);
509 JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject()));
511 JS::CompileOptions options(cx, JSVERSION_DEFAULT);
512 options.forceAsync = true;
513 options.compileAndGo = true;
514 options.installedFile = true;
516 nsCOMPtr<nsIURI> uri;
517 mChannel->GetURI(getter_AddRefs(uri));
518 nsAutoCString spec;
519 uri->GetSpec(spec);
520 options.setFile(spec.get());
522 if (!JS::CanCompileOffThread(cx, options, mScriptLength)) {
523 NS_WARNING("Can't compile script off thread!");
524 return NS_OK;
525 }
527 nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable =
528 new NotifyPrecompilationCompleteRunnable(this);
530 if (!JS::CompileOffThread(cx, options,
531 mScriptBuf, mScriptLength,
532 OffThreadCallback,
533 static_cast<void*>(runnable))) {
534 NS_WARNING("Failed to compile script off thread!");
535 return NS_OK;
536 }
538 unused << runnable.forget();
539 notifier.Disarm();
541 return NS_OK;
542 }
544 /* static */
545 void
546 ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData)
547 {
548 nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable =
549 dont_AddRef(static_cast<NotifyPrecompilationCompleteRunnable*>(aData));
550 runnable->SetToken(aToken);
552 NS_DispatchToMainThread(runnable);
553 }
555 void
556 ScriptPrecompiler::SendObserverNotification()
557 {
558 MOZ_ASSERT(mChannel && mObserver);
559 MOZ_ASSERT(NS_IsMainThread());
561 nsCOMPtr<nsIURI> uri;
562 mChannel->GetURI(getter_AddRefs(uri));
563 mObserver->Observe(uri, "script-precompiled", nullptr);
564 }
566 NS_IMETHODIMP
567 mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI,
568 nsIPrincipal* aPrincipal,
569 nsIObserver *aObserver)
570 {
571 nsCOMPtr<nsIChannel> channel;
572 nsresult rv = NS_NewChannel(getter_AddRefs(channel),
573 aURI, nullptr, nullptr, nullptr,
574 nsIRequest::LOAD_NORMAL, nullptr);
575 NS_ENSURE_SUCCESS(rv, rv);
577 nsRefPtr<ScriptPrecompiler> loadObserver =
578 new ScriptPrecompiler(aObserver, aPrincipal, channel);
580 nsCOMPtr<nsIStreamLoader> loader;
581 rv = NS_NewStreamLoader(getter_AddRefs(loader), loadObserver);
582 NS_ENSURE_SUCCESS(rv, rv);
584 nsCOMPtr<nsIStreamListener> listener = loader.get();
585 rv = channel->AsyncOpen(listener, nullptr);
586 NS_ENSURE_SUCCESS(rv, rv);
588 return NS_OK;
589 }