js/xpconnect/loader/mozJSSubScriptLoader.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 }

mercurial