summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/loader/mozJSSubScriptLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/loader/mozJSSubScriptLoader.cpp')
-rw-r--r--js/xpconnect/loader/mozJSSubScriptLoader.cpp929
1 files changed, 929 insertions, 0 deletions
diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
new file mode 100644
index 000000000..824e9ab9e
--- /dev/null
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -0,0 +1,929 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozJSSubScriptLoader.h"
+#include "mozJSComponentLoader.h"
+#include "mozJSLoaderUtils.h"
+
+#include "nsIURI.h"
+#include "nsIIOService.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsScriptLoader.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsThreadUtils.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsJSPrincipals.h"
+#include "xpcprivate.h" // For xpc::OptionsBase
+#include "jswrapper.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsStringGlue.h"
+#include "nsCycleCollectionParticipant.h"
+
+using namespace mozilla::scache;
+using namespace JS;
+using namespace xpc;
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase {
+public:
+ explicit LoadSubScriptOptions(JSContext* cx = xpc_GetSafeJSContext(),
+ JSObject* options = nullptr)
+ : OptionsBase(cx, options)
+ , target(cx)
+ , charset(NullString())
+ , ignoreCache(false)
+ , async(false)
+ { }
+
+ virtual bool Parse() {
+ return ParseObject("target", &target) &&
+ ParseString("charset", charset) &&
+ ParseBoolean("ignoreCache", &ignoreCache) &&
+ ParseBoolean("async", &async);
+ }
+
+ RootedObject target;
+ nsString charset;
+ bool ignoreCache;
+ bool async;
+};
+
+
+/* load() error msgs, XXX localize? */
+#define LOAD_ERROR_NOSERVICE "Error creating IO Service."
+#define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)"
+#define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad."
+#define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI."
+#define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)"
+#define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)"
+#define LOAD_ERROR_BADCHARSET "Error converting to specified charset"
+#define LOAD_ERROR_BADREAD "File Read Error."
+#define LOAD_ERROR_READUNDERFLOW "File Read Error (underflow.)"
+#define LOAD_ERROR_NOPRINCIPALS "Failed to get principals."
+#define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad."
+#define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large"
+
+mozJSSubScriptLoader::mozJSSubScriptLoader() : mSystemPrincipal(nullptr)
+{
+}
+
+mozJSSubScriptLoader::~mozJSSubScriptLoader()
+{
+ /* empty */
+}
+
+NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader)
+
+static void
+ReportError(JSContext* cx, const char* msg)
+{
+ RootedValue exn(cx, JS::StringValue(JS_NewStringCopyZ(cx, msg)));
+ JS_SetPendingException(cx, exn);
+}
+
+static void
+ReportError(JSContext* cx, const char* origMsg, nsIURI* uri)
+{
+ if (!uri) {
+ ReportError(cx, origMsg);
+ return;
+ }
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv))
+ spec.Assign("(unknown)");
+
+ nsAutoCString msg(origMsg);
+ msg.Append(": ");
+ msg.Append(spec);
+ ReportError(cx, msg.get());
+}
+
+bool
+PrepareScript(nsIURI* uri,
+ JSContext* cx,
+ RootedObject& targetObj,
+ const char* uriStr,
+ const nsAString& charset,
+ const char* buf,
+ int64_t len,
+ bool reuseGlobal,
+ MutableHandleScript script,
+ MutableHandleFunction function)
+{
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(uriStr, 1)
+ .setVersion(JSVERSION_LATEST);
+ if (!charset.IsVoid()) {
+ char16_t* scriptBuf = nullptr;
+ size_t scriptLength = 0;
+
+ nsresult rv =
+ nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len,
+ charset, nullptr, scriptBuf, scriptLength);
+
+ JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
+ JS::SourceBufferHolder::GiveOwnership);
+
+ if (NS_FAILED(rv)) {
+ ReportError(cx, LOAD_ERROR_BADCHARSET, uri);
+ return false;
+ }
+
+ if (!reuseGlobal) {
+ if (JS_IsGlobalObject(targetObj)) {
+ return JS::Compile(cx, options, srcBuf, script);
+ }
+ return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script);
+ } else {
+ AutoObjectVector envChain(cx);
+ if (!JS_IsGlobalObject(targetObj) && !envChain.append(targetObj)) {
+ return false;
+ }
+ return JS::CompileFunction(cx, envChain, options, nullptr, 0, nullptr,
+ srcBuf, function);
+ }
+ } else {
+ // We only use lazy source when no special encoding is specified because
+ // the lazy source loader doesn't know the encoding.
+ if (!reuseGlobal) {
+ options.setSourceIsLazy(true);
+ if (JS_IsGlobalObject(targetObj)) {
+ return JS::Compile(cx, options, buf, len, script);
+ }
+ return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
+ } else {
+ AutoObjectVector envChain(cx);
+ if (!JS_IsGlobalObject(targetObj) && !envChain.append(targetObj)) {
+ return false;
+ }
+ return JS::CompileFunction(cx, envChain, options, nullptr, 0, nullptr,
+ buf, len, function);
+ }
+ }
+}
+
+bool
+EvalScript(JSContext* cx,
+ RootedObject& target_obj,
+ MutableHandleValue retval,
+ nsIURI* uri,
+ bool cache,
+ RootedScript& script,
+ RootedFunction& function)
+{
+ if (function) {
+ script = JS_GetFunctionScript(cx, function);
+ }
+
+ if (function) {
+ if (!JS_CallFunction(cx, target_obj, function, JS::HandleValueArray::empty(), retval)) {
+ return false;
+ }
+ } else {
+ if (JS_IsGlobalObject(target_obj)) {
+ if (!JS_ExecuteScript(cx, script, retval)) {
+ return false;
+ }
+ } else {
+ JS::AutoObjectVector envChain(cx);
+ if (!envChain.append(target_obj)) {
+ return false;
+ }
+ if (!JS_ExecuteScript(cx, envChain, script, retval)) {
+ return false;
+ }
+ }
+ }
+
+ JSAutoCompartment rac(cx, target_obj);
+ if (!JS_WrapValue(cx, retval)) {
+ return false;
+ }
+
+ if (cache && !!script) {
+ nsAutoCString cachePath;
+ JSVersion version = JS_GetVersion(cx);
+ cachePath.AppendPrintf("jssubloader/%d", version);
+ PathifyURI(uri, cachePath);
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ if (!secman) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = secman->GetSystemPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv) || !principal) {
+ ReportError(cx, LOAD_ERROR_NOPRINCIPALS, uri);
+ return false;
+ }
+
+ WriteCachedScript(StartupCache::GetSingleton(),
+ cachePath, cx, principal, script);
+ }
+
+ return true;
+}
+
+class AsyncScriptLoader : public nsIIncrementalStreamLoaderObserver
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader)
+
+ AsyncScriptLoader(nsIChannel* aChannel, bool aReuseGlobal,
+ JSObject* aTargetObj, const nsAString& aCharset,
+ bool aCache, Promise* aPromise)
+ : mChannel(aChannel)
+ , mTargetObj(aTargetObj)
+ , mPromise(aPromise)
+ , mCharset(aCharset)
+ , mReuseGlobal(aReuseGlobal)
+ , mCache(aCache)
+ {
+ // Needed for the cycle collector to manage mTargetObj.
+ mozilla::HoldJSObjects(this);
+ }
+
+private:
+ virtual ~AsyncScriptLoader() {
+ mozilla::DropJSObjects(this);
+ }
+
+ RefPtr<nsIChannel> mChannel;
+ Heap<JSObject*> mTargetObj;
+ RefPtr<Promise> mPromise;
+ nsString mCharset;
+ bool mReuseGlobal;
+ bool mCache;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader)
+ NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->mTargetObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader)
+
+class MOZ_STACK_CLASS AutoRejectPromise
+{
+public:
+ AutoRejectPromise(AutoEntryScript& aAutoEntryScript,
+ Promise* aPromise,
+ nsIGlobalObject* aGlobalObject)
+ : mAutoEntryScript(aAutoEntryScript)
+ , mPromise(aPromise)
+ , mGlobalObject(aGlobalObject) {}
+
+ ~AutoRejectPromise() {
+ if (mPromise) {
+ JSContext* cx = mAutoEntryScript.cx();
+ RootedValue rejectionValue(cx, JS::UndefinedValue());
+ if (mAutoEntryScript.HasException()) {
+ Unused << mAutoEntryScript.PeekException(&rejectionValue);
+ }
+ mPromise->MaybeReject(cx, rejectionValue);
+ }
+ }
+
+ void ResolvePromise(HandleValue aResolveValue) {
+ mPromise->MaybeResolve(aResolveValue);
+ mPromise = nullptr;
+ }
+
+private:
+ AutoEntryScript& mAutoEntryScript;
+ RefPtr<Promise> mPromise;
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+};
+
+NS_IMETHODIMP
+AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ uint32_t aDataLength,
+ const uint8_t* aData,
+ uint32_t *aConsumedData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ uint32_t aLength,
+ const uint8_t* aBuf)
+{
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(mTargetObj);
+ AutoEntryScript aes(globalObject, "async loadSubScript");
+ AutoRejectPromise autoPromise(aes, mPromise, globalObject);
+ JSContext* cx = aes.cx();
+
+ if (NS_FAILED(aStatus)) {
+ ReportError(cx, "Unable to load script.", uri);
+ }
+ // Just notify that we are done with this load.
+ NS_ENSURE_SUCCESS(aStatus, NS_OK);
+
+ if (aLength == 0) {
+ ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
+ return NS_OK;
+ }
+
+ if (aLength > INT32_MAX) {
+ ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
+ return NS_OK;
+ }
+
+ RootedFunction function(cx);
+ RootedScript script(cx);
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RootedObject target_obj(cx, mTargetObj);
+
+ if (!PrepareScript(uri, cx, target_obj, spec.get(), mCharset,
+ reinterpret_cast<const char*>(aBuf), aLength,
+ mReuseGlobal, &script, &function))
+ {
+ return NS_OK;
+ }
+
+ JS::Rooted<JS::Value> retval(cx);
+ if (EvalScript(cx, target_obj, &retval, uri, mCache, script, function)) {
+ autoPromise.ResolvePromise(retval);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri, JSObject* targetObjArg,
+ const nsAString& charset,
+ nsIIOService* serv, bool reuseGlobal,
+ bool cache, MutableHandleValue retval)
+{
+ RootedObject target_obj(RootingCx(), targetObjArg);
+
+ nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(target_obj);
+ ErrorResult result;
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(globalObject))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ DebugOnly<bool> asJS = ToJSValue(jsapi.cx(), promise, retval);
+ MOZ_ASSERT(asJS, "Should not fail to convert the promise to a JS value");
+
+ // We create a channel and call SetContentType, to avoid expensive MIME type
+ // lookups (bug 632490).
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ serv);
+
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ channel->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
+
+ RefPtr<AsyncScriptLoader> loadObserver =
+ new AsyncScriptLoader(channel,
+ reuseGlobal,
+ target_obj,
+ charset,
+ cache,
+ promise);
+
+ nsCOMPtr<nsIIncrementalStreamLoader> loader;
+ rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener = loader.get();
+ return channel->AsyncOpen2(listener);
+}
+
+bool
+mozJSSubScriptLoader::ReadScript(nsIURI* uri, JSContext* cx, JSObject* targetObjArg,
+ const nsAString& charset, const char* uriStr,
+ nsIIOService* serv, nsIPrincipal* principal,
+ bool reuseGlobal, JS::MutableHandleScript script,
+ JS::MutableHandleFunction function)
+{
+ script.set(nullptr);
+ function.set(nullptr);
+
+ RootedObject target_obj(cx, targetObjArg);
+
+ // We create a channel and call SetContentType, to avoid expensive MIME type
+ // lookups (bug 632490).
+ nsCOMPtr<nsIChannel> chan;
+ nsCOMPtr<nsIInputStream> instream;
+ nsresult rv;
+ rv = NS_NewChannel(getter_AddRefs(chan),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ serv);
+
+ if (NS_SUCCEEDED(rv)) {
+ chan->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
+ rv = chan->Open2(getter_AddRefs(instream));
+ }
+
+ if (NS_FAILED(rv)) {
+ ReportError(cx, LOAD_ERROR_NOSTREAM, uri);
+ return false;
+ }
+
+ int64_t len = -1;
+
+ rv = chan->GetContentLength(&len);
+ if (NS_FAILED(rv) || len == -1) {
+ ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
+ return false;
+ }
+
+ if (len > INT32_MAX) {
+ ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
+ return false;
+ }
+
+ nsCString buf;
+ rv = NS_ReadInputStreamToString(instream, buf, len);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return PrepareScript(uri, cx, target_obj, uriStr, charset,
+ buf.get(), len,
+ reuseGlobal,
+ script, function);
+}
+
+NS_IMETHODIMP
+mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
+ HandleValue target,
+ const nsAString& charset,
+ JSContext* cx,
+ MutableHandleValue retval)
+{
+ /*
+ * Loads a local url and evals it into the current cx
+ * Synchronous (an async version would be cool too.)
+ * url: The url to load. Must be local so that it can be loaded
+ * synchronously.
+ * target_obj: Optional object to eval the script onto (defaults to context
+ * global)
+ * charset: Optional character set to use for reading
+ * returns: Whatever jsval the script pointed to by the url returns.
+ * Should ONLY (O N L Y !) be called from JavaScript code.
+ */
+ LoadSubScriptOptions options(cx);
+ options.charset = charset;
+ options.target = target.isObject() ? &target.toObject() : nullptr;
+ return DoLoadSubScriptWithOptions(url, options, cx, retval);
+}
+
+
+NS_IMETHODIMP
+mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url,
+ HandleValue optionsVal,
+ JSContext* cx,
+ MutableHandleValue retval)
+{
+ if (!optionsVal.isObject())
+ return NS_ERROR_INVALID_ARG;
+ LoadSubScriptOptions options(cx, &optionsVal.toObject());
+ if (!options.Parse())
+ return NS_ERROR_INVALID_ARG;
+ return DoLoadSubScriptWithOptions(url, options, cx, retval);
+}
+
+nsresult
+mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
+ LoadSubScriptOptions& options,
+ JSContext* cx,
+ MutableHandleValue retval)
+{
+ nsresult rv = NS_OK;
+
+ /* set the system principal if it's not here already */
+ if (!mSystemPrincipal) {
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ if (!secman)
+ return NS_OK;
+
+ rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal));
+ if (NS_FAILED(rv) || !mSystemPrincipal)
+ return rv;
+ }
+
+ RootedObject targetObj(cx);
+ mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+ rv = loader->FindTargetObject(cx, &targetObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We base reusingGlobal off of what the loader told us, but we may not
+ // actually be using that object.
+ bool reusingGlobal = !JS_IsGlobalObject(targetObj);
+
+ if (options.target)
+ targetObj = options.target;
+
+ // Remember an object out of the calling compartment so that we
+ // can properly wrap the result later.
+ nsCOMPtr<nsIPrincipal> principal = mSystemPrincipal;
+ RootedObject result_obj(cx, targetObj);
+ targetObj = JS_FindCompilationScope(cx, targetObj);
+ if (!targetObj)
+ return NS_ERROR_FAILURE;
+
+ if (targetObj != result_obj)
+ principal = GetObjectPrincipal(targetObj);
+
+ /* load up the url. From here on, failures are reflected as ``custom''
+ * js exceptions */
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString uriStr;
+ nsAutoCString scheme;
+
+ // Figure out who's calling us
+ JS::AutoFilename filename;
+ if (!JS::DescribeScriptedCaller(cx, &filename)) {
+ // No scripted frame means we don't know who's calling, bail.
+ return NS_ERROR_FAILURE;
+ }
+
+ JSAutoCompartment ac(cx, targetObj);
+
+ // Suppress caching if we're compiling as content.
+ StartupCache* cache = (principal == mSystemPrincipal)
+ ? StartupCache::GetSingleton()
+ : nullptr;
+ nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
+ if (!serv) {
+ ReportError(cx, LOAD_ERROR_NOSERVICE);
+ return NS_OK;
+ }
+
+ // Make sure to explicitly create the URI, since we'll need the
+ // canonicalized spec.
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LossyConvertUTF16toASCII(url).get(), nullptr, serv);
+ if (NS_FAILED(rv)) {
+ ReportError(cx, LOAD_ERROR_NOURI);
+ return NS_OK;
+ }
+
+ rv = uri->GetSpec(uriStr);
+ if (NS_FAILED(rv)) {
+ ReportError(cx, LOAD_ERROR_NOSPEC);
+ return NS_OK;
+ }
+
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ ReportError(cx, LOAD_ERROR_NOSCHEME, uri);
+ return NS_OK;
+ }
+
+ if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app")) {
+ // This might be a URI to a local file, though!
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
+ if (!fileURL) {
+ ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL, uri);
+ return NS_OK;
+ }
+
+ // For file URIs prepend the filename with the filename of the
+ // calling script, and " -> ". See bug 418356.
+ nsAutoCString tmp(filename.get());
+ tmp.AppendLiteral(" -> ");
+ tmp.Append(uriStr);
+
+ uriStr = tmp;
+ }
+
+ JSVersion version = JS_GetVersion(cx);
+ nsAutoCString cachePath;
+ cachePath.AppendPrintf("jssubloader/%d", version);
+ PathifyURI(uri, cachePath);
+
+ RootedFunction function(cx);
+ RootedScript script(cx);
+ if (cache && !options.ignoreCache) {
+ rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
+ if (NS_FAILED(rv)) {
+ // ReadCachedScript may have set a pending exception.
+ JS_ClearPendingException(cx);
+ }
+ }
+
+ // If we are doing an async load, trigger it and bail out.
+ if (!script && options.async) {
+ return ReadScriptAsync(uri, targetObj, options.charset, serv,
+ reusingGlobal, !!cache, retval);
+ }
+
+ if (!script) {
+ if (!ReadScript(uri, cx, targetObj, options.charset,
+ static_cast<const char*>(uriStr.get()), serv,
+ principal, reusingGlobal, &script, &function))
+ {
+ return NS_OK;
+ }
+ } else {
+ cache = nullptr;
+ }
+
+ Unused << EvalScript(cx, targetObj, retval, uri, !!cache, script, function);
+ return NS_OK;
+}
+
+/**
+ * Let us compile scripts from a URI off the main thread.
+ */
+
+class ScriptPrecompiler : public nsIIncrementalStreamLoaderObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+ ScriptPrecompiler(nsIObserver* aObserver,
+ nsIPrincipal* aPrincipal,
+ nsIChannel* aChannel)
+ : mObserver(aObserver)
+ , mPrincipal(aPrincipal)
+ , mChannel(aChannel)
+ , mScriptBuf(nullptr)
+ , mScriptLength(0)
+ {}
+
+ static void OffThreadCallback(void* aToken, void* aData);
+
+ /* Sends the "done" notification back. Main thread only. */
+ void SendObserverNotification();
+
+private:
+ virtual ~ScriptPrecompiler()
+ {
+ if (mScriptBuf) {
+ js_free(mScriptBuf);
+ }
+ }
+
+ RefPtr<nsIObserver> mObserver;
+ RefPtr<nsIPrincipal> mPrincipal;
+ RefPtr<nsIChannel> mChannel;
+ char16_t* mScriptBuf;
+ size_t mScriptLength;
+};
+
+NS_IMPL_ISUPPORTS(ScriptPrecompiler, nsIIncrementalStreamLoaderObserver);
+
+class NotifyPrecompilationCompleteRunnable : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ explicit NotifyPrecompilationCompleteRunnable(ScriptPrecompiler* aPrecompiler)
+ : mPrecompiler(aPrecompiler)
+ , mToken(nullptr)
+ {}
+
+ void SetToken(void* aToken) {
+ MOZ_ASSERT(aToken && !mToken);
+ mToken = aToken;
+ }
+
+protected:
+ RefPtr<ScriptPrecompiler> mPrecompiler;
+ void* mToken;
+};
+
+/* RAII helper class to send observer notifications */
+class AutoSendObserverNotification {
+public:
+ explicit AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler)
+ : mPrecompiler(aPrecompiler)
+ {}
+
+ ~AutoSendObserverNotification() {
+ if (mPrecompiler) {
+ mPrecompiler->SendObserverNotification();
+ }
+ }
+
+ void Disarm() {
+ mPrecompiler = nullptr;
+ }
+
+private:
+ ScriptPrecompiler* mPrecompiler;
+};
+
+NS_IMETHODIMP
+NotifyPrecompilationCompleteRunnable::Run(void)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPrecompiler);
+
+ AutoSendObserverNotification notifier(mPrecompiler);
+
+ if (mToken) {
+ JSContext* cx = XPCJSContext::Get()->Context();
+ NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
+ JS::CancelOffThreadScript(cx, mToken);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptPrecompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ uint32_t aDataLength,
+ const uint8_t* aData,
+ uint32_t *aConsumedData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptPrecompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ uint32_t aLength,
+ const uint8_t* aString)
+{
+ AutoSendObserverNotification notifier(this);
+
+ // Just notify that we are done with this load.
+ NS_ENSURE_SUCCESS(aStatus, NS_OK);
+
+ // Convert data to char16_t* and prepare to call CompileOffThread.
+ nsAutoString hintCharset;
+ nsresult rv =
+ nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
+ hintCharset, nullptr,
+ mScriptBuf, mScriptLength);
+
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // Our goal is to cache persistently the compiled script and to avoid quota
+ // checks. Since the caching mechanism decide the persistence type based on
+ // the principal, we create a new global with the app's principal.
+ // We then enter its compartment to compile with its principal.
+ AutoSafeJSContext cx;
+ RootedValue v(cx);
+ SandboxOptions sandboxOptions;
+ sandboxOptions.sandboxName.AssignASCII("asm.js precompilation");
+ sandboxOptions.invisibleToDebugger = true;
+ sandboxOptions.discardSource = true;
+ rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject()));
+
+ JS::CompileOptions options(cx, JSVERSION_DEFAULT);
+ options.forceAsync = true;
+ options.installedFile = true;
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ options.setFile(spec.get());
+
+ if (!JS::CanCompileOffThread(cx, options, mScriptLength)) {
+ NS_WARNING("Can't compile script off thread!");
+ return NS_OK;
+ }
+
+ RefPtr<NotifyPrecompilationCompleteRunnable> runnable =
+ new NotifyPrecompilationCompleteRunnable(this);
+
+ if (!JS::CompileOffThread(cx, options,
+ mScriptBuf, mScriptLength,
+ OffThreadCallback,
+ static_cast<void*>(runnable))) {
+ NS_WARNING("Failed to compile script off thread!");
+ return NS_OK;
+ }
+
+ Unused << runnable.forget();
+ notifier.Disarm();
+
+ return NS_OK;
+}
+
+/* static */
+void
+ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData)
+{
+ RefPtr<NotifyPrecompilationCompleteRunnable> runnable =
+ dont_AddRef(static_cast<NotifyPrecompilationCompleteRunnable*>(aData));
+ runnable->SetToken(aToken);
+
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+ScriptPrecompiler::SendObserverNotification()
+{
+ MOZ_ASSERT(mChannel && mObserver);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ mObserver->Observe(uri, "script-precompiled", nullptr);
+}
+
+NS_IMETHODIMP
+mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ nsIObserver* aObserver)
+{
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ aURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<ScriptPrecompiler> loadObserver =
+ new ScriptPrecompiler(aObserver, aPrincipal, channel);
+
+ nsCOMPtr<nsIIncrementalStreamLoader> loader;
+ rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener = loader.get();
+ rv = channel->AsyncOpen2(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}