gecko-dev/js/xpconnect/loader/mozJSComponentLoader.cpp

1388 строки
44 KiB
C++

/* -*- 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 "mozilla/Attributes.h"
#include <cstdarg>
#include "mozilla/Logging.h"
#ifdef ANDROID
#include <android/log.h>
#endif
#ifdef XP_WIN
#include <windows.h>
#endif
#include "jsapi.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsIComponentManager.h"
#include "mozilla/Module.h"
#include "nsIFile.h"
#include "mozJSComponentLoader.h"
#include "mozJSLoaderUtils.h"
#include "nsIXPConnect.h"
#include "nsIObserverService.h"
#include "nsIScriptSecurityManager.h"
#include "nsIFileURL.h"
#include "nsIJARURI.h"
#include "nsNetUtil.h"
#include "jsprf.h"
#include "nsJSPrincipals.h"
#include "nsJSUtils.h"
#include "xpcprivate.h"
#include "xpcpublic.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
#include "WrapperFactory.h"
#include "mozilla/AddonPathService.h"
#include "mozilla/scache/StartupCache.h"
#include "mozilla/scache/StartupCacheUtils.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ScriptSettings.h"
using namespace mozilla;
using namespace mozilla::scache;
using namespace xpc;
using namespace JS;
// This JSClass exists to trick silly code that expects toString()ing the
// global in a component scope to return something with "BackstagePass" in it
// to continue working.
static const JSClass kFakeBackstagePassJSClass = { "FakeBackstagePass" };
static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1";
static const char kObserverServiceContractID[] = "@mozilla.org/observer-service;1";
static const char kJSCachePrefix[] = "jsloader";
#define HAVE_PR_MEMMAP
/**
* Buffer sizes for serialization and deserialization of scripts.
* FIXME: bug #411579 (tune this macro!) Last updated: Jan 2008
*/
#define XPC_SERIALIZATION_BUFFER_SIZE (64 * 1024)
#define XPC_DESERIALIZATION_BUFFER_SIZE (12 * 8192)
// NSPR_LOG_MODULES=JSComponentLoader:5
static PRLogModuleInfo* gJSCLLog;
#define LOG(args) MOZ_LOG(gJSCLLog, mozilla::LogLevel::Debug, args)
// Components.utils.import error messages
#define ERROR_SCOPE_OBJ "%s - Second argument must be an object."
#define ERROR_NOT_PRESENT "%s - EXPORTED_SYMBOLS is not present."
#define ERROR_NOT_AN_ARRAY "%s - EXPORTED_SYMBOLS is not an array."
#define ERROR_GETTING_ARRAY_LENGTH "%s - Error getting array length of EXPORTED_SYMBOLS."
#define ERROR_ARRAY_ELEMENT "%s - EXPORTED_SYMBOLS[%d] is not a string."
#define ERROR_GETTING_SYMBOL "%s - Could not get symbol '%s'."
#define ERROR_SETTING_SYMBOL "%s - Could not set symbol '%s' on target object."
static bool
Dump(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0)
return true;
RootedString str(cx, JS::ToString(cx, args[0]));
if (!str)
return false;
JSAutoByteString utf8str;
if (!utf8str.encodeUtf8(cx, str))
return false;
#ifdef ANDROID
__android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.ptr());
#endif
#ifdef XP_WIN
if (IsDebuggerPresent()) {
nsAutoJSString wstr;
if (!wstr.init(cx, str))
return false;
OutputDebugStringW(wstr.get());
}
#endif
fputs(utf8str.ptr(), stdout);
fflush(stdout);
return true;
}
static bool
Debug(JSContext* cx, unsigned argc, Value* vp)
{
#ifdef DEBUG
return Dump(cx, argc, vp);
#else
return true;
#endif
}
static const JSFunctionSpec gGlobalFun[] = {
JS_FS("dump", Dump, 1,0),
JS_FS("debug", Debug, 1,0),
JS_FS("atob", Atob, 1,0),
JS_FS("btoa", Btoa, 1,0),
JS_FS_END
};
class MOZ_STACK_CLASS JSCLContextHelper
{
public:
explicit JSCLContextHelper(JSContext* aCx);
~JSCLContextHelper();
void reportErrorAfterPop(char* buf);
private:
JSContext* mContext;
char* mBuf;
// prevent copying and assignment
JSCLContextHelper(const JSCLContextHelper&) = delete;
const JSCLContextHelper& operator=(const JSCLContextHelper&) = delete;
};
static nsresult
ReportOnCaller(JSContext* callerContext,
const char* format, ...) {
if (!callerContext) {
return NS_ERROR_FAILURE;
}
va_list ap;
va_start(ap, format);
char* buf = JS_vsmprintf(format, ap);
if (!buf) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS_ReportError(callerContext, buf);
JS_smprintf_free(buf);
return NS_OK;
}
static nsresult
ReportOnCaller(JSCLContextHelper& helper,
const char* format, ...)
{
va_list ap;
va_start(ap, format);
char* buf = JS_vsmprintf(format, ap);
if (!buf) {
return NS_ERROR_OUT_OF_MEMORY;
}
helper.reportErrorAfterPop(buf);
return NS_OK;
}
mozJSComponentLoader::mozJSComponentLoader()
: mModules(16),
mImports(16),
mInProgressImports(16),
mInitialized(false),
mReuseLoaderGlobal(false)
{
MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton");
if (!gJSCLLog) {
gJSCLLog = PR_NewLogModule("JSComponentLoader");
}
sSelf = this;
}
#define ENSURE_DEP(name) { nsresult rv = Ensure##name(); NS_ENSURE_SUCCESS(rv, rv); }
#define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__));
#define BEGIN_ENSURE(self, ...) { \
if (m##self) \
return NS_OK; \
ENSURE_DEPS(__VA_ARGS__); \
}
class MOZ_STACK_CLASS ComponentLoaderInfo {
public:
explicit ComponentLoaderInfo(const nsACString& aLocation) : mLocation(aLocation) {}
nsIIOService* IOService() { MOZ_ASSERT(mIOService); return mIOService; }
nsresult EnsureIOService() {
if (mIOService)
return NS_OK;
nsresult rv;
mIOService = do_GetIOService(&rv);
return rv;
}
nsIURI* URI() { MOZ_ASSERT(mURI); return mURI; }
nsresult EnsureURI() {
BEGIN_ENSURE(URI, IOService);
return mIOService->NewURI(mLocation, nullptr, nullptr, getter_AddRefs(mURI));
}
nsIChannel* ScriptChannel() { MOZ_ASSERT(mScriptChannel); return mScriptChannel; }
nsresult EnsureScriptChannel() {
BEGIN_ENSURE(ScriptChannel, IOService, URI);
return NS_NewChannel(getter_AddRefs(mScriptChannel),
mURI,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_NORMAL,
nsIContentPolicy::TYPE_SCRIPT,
nullptr, // aLoadGroup
nullptr, // aCallbacks
nsIRequest::LOAD_NORMAL,
mIOService);
}
nsIURI* ResolvedURI() { MOZ_ASSERT(mResolvedURI); return mResolvedURI; }
nsresult EnsureResolvedURI() {
BEGIN_ENSURE(ResolvedURI, ScriptChannel);
return mScriptChannel->GetURI(getter_AddRefs(mResolvedURI));
}
nsAutoCString& Key() { return *mKey; }
nsresult EnsureKey() {
ENSURE_DEPS(ResolvedURI);
mKey.emplace();
return mResolvedURI->GetSpec(*mKey);
}
private:
const nsACString& mLocation;
nsCOMPtr<nsIIOService> mIOService;
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIChannel> mScriptChannel;
nsCOMPtr<nsIURI> mResolvedURI;
Maybe<nsAutoCString> mKey; // This is safe because we're MOZ_STACK_CLASS
};
#undef BEGIN_ENSURE
#undef ENSURE_DEPS
#undef ENSURE_DEP
mozJSComponentLoader::~mozJSComponentLoader()
{
if (mInitialized) {
NS_ERROR("'xpcom-shutdown-loaders' was not fired before cleaning up mozJSComponentLoader");
UnloadModules();
}
sSelf = nullptr;
}
mozJSComponentLoader*
mozJSComponentLoader::sSelf;
NS_IMPL_ISUPPORTS(mozJSComponentLoader,
mozilla::ModuleLoader,
xpcIJSModuleLoader,
nsIObserver)
nsresult
mozJSComponentLoader::ReallyInit()
{
nsresult rv;
mReuseLoaderGlobal = Preferences::GetBool("jsloader.reuseGlobal");
// XXXkhuey B2G child processes have some sort of preferences race that
// results in getting the wrong value.
// But we don't want that on Firefox Mulet as it break most Firefox JSMs...
// Also disable on debug builds to break js components that rely on this.
#if defined(MOZ_B2G) && !defined(MOZ_MULET) && !defined(MOZ_B2GDROID) && !defined(DEBUG)
mReuseLoaderGlobal = true;
#endif
nsCOMPtr<nsIScriptSecurityManager> secman =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
if (!secman)
return NS_ERROR_FAILURE;
rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal));
if (NS_FAILED(rv) || !mSystemPrincipal)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIObserverService> obsSvc =
do_GetService(kObserverServiceContractID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false);
NS_ENSURE_SUCCESS(rv, rv);
mInitialized = true;
return NS_OK;
}
// For terrible compatibility reasons, we need to consider both the global
// lexical scope and the global of modules when searching for exported
// symbols.
static JSObject*
ResolveModuleObjectProperty(JSContext* aCx, HandleObject aModObj, const char* name)
{
if (JS_HasExtensibleLexicalScope(aModObj)) {
RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj));
bool found;
if (!JS_HasOwnProperty(aCx, lexical, name, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
const mozilla::Module*
mozJSComponentLoader::LoadModule(FileLocation& aFile)
{
nsCOMPtr<nsIFile> file = aFile.GetBaseFile();
nsCString spec;
aFile.GetURIString(spec);
ComponentLoaderInfo info(spec);
nsresult rv = info.EnsureURI();
NS_ENSURE_SUCCESS(rv, nullptr);
if (!mInitialized) {
rv = ReallyInit();
if (NS_FAILED(rv))
return nullptr;
}
ModuleEntry* mod;
if (mModules.Get(spec, &mod))
return mod;
dom::AutoJSAPI jsapi;
jsapi.Init();
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
nsAutoPtr<ModuleEntry> entry(new ModuleEntry(cx));
RootedValue dummy(cx);
rv = ObjectForLocation(info, file, &entry->obj, &entry->thisObjectKey,
&entry->location, false, &dummy);
if (NS_FAILED(rv)) {
return nullptr;
}
nsCOMPtr<nsIXPConnect> xpc = do_GetService(kXPConnectServiceContractID,
&rv);
if (NS_FAILED(rv))
return nullptr;
nsCOMPtr<nsIComponentManager> cm;
rv = NS_GetComponentManager(getter_AddRefs(cm));
if (NS_FAILED(rv))
return nullptr;
JSAutoCompartment ac(cx, entry->obj);
RootedObject entryObj(cx, entry->obj);
RootedObject NSGetFactoryHolder(cx, ResolveModuleObjectProperty(cx, entryObj, "NSGetFactory"));
RootedValue NSGetFactory_val(cx);
if (!NSGetFactoryHolder ||
!JS_GetProperty(cx, NSGetFactoryHolder, "NSGetFactory", &NSGetFactory_val) ||
NSGetFactory_val.isUndefined())
{
return nullptr;
}
if (JS_TypeOfValue(cx, NSGetFactory_val) != JSTYPE_FUNCTION) {
JS_ReportError(cx, "%s has NSGetFactory property that is not a function",
spec.get());
return nullptr;
}
RootedObject jsGetFactoryObj(cx);
if (!JS_ValueToObject(cx, NSGetFactory_val, &jsGetFactoryObj) ||
!jsGetFactoryObj) {
/* XXX report error properly */
return nullptr;
}
rv = xpc->WrapJS(cx, jsGetFactoryObj,
NS_GET_IID(xpcIJSGetFactory), getter_AddRefs(entry->getfactoryobj));
if (NS_FAILED(rv)) {
/* XXX report error properly */
#ifdef DEBUG
fprintf(stderr, "mJCL: couldn't get nsIModule from jsval\n");
#endif
return nullptr;
}
// Cache this module for later
mModules.Put(spec, entry);
// Set the location information for the new global, so that tools like
// about:memory may use that information
if (!mReuseLoaderGlobal) {
xpc::SetLocationForGlobal(entryObj, spec);
}
// The hash owns the ModuleEntry now, forget about it
return entry.forget();
}
nsresult
mozJSComponentLoader::FindTargetObject(JSContext* aCx,
MutableHandleObject aTargetObject)
{
aTargetObject.set(nullptr);
RootedObject targetObject(aCx);
if (mReuseLoaderGlobal) {
JSFunction* fun = js::GetOutermostEnclosingFunctionOfScriptedCaller(aCx);
if (fun) {
JSObject* funParent = js::GetNearestEnclosingWithScopeObjectForFunction(fun);
if (JS_GetClass(funParent) == &kFakeBackstagePassJSClass)
targetObject = funParent;
}
}
// The above could fail, even if mReuseLoaderGlobal, if the scripted
// caller is not a component/JSM (it could be a DOM scope, for
// instance).
if (!targetObject) {
// Our targetObject is the caller's global object. Let's get it.
targetObject = CurrentGlobalOrNull(aCx);
}
aTargetObject.set(targetObject);
return NS_OK;
}
// This requires that the keys be strings and the values be pointers.
template <class Key, class Data, class UserData>
static size_t
SizeOfTableExcludingThis(const nsBaseHashtable<Key, Data, UserData>& aTable,
MallocSizeOf aMallocSizeOf)
{
size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
}
return n;
}
size_t
mozJSComponentLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
{
size_t n = aMallocSizeOf(this);
n += SizeOfTableExcludingThis(mModules, aMallocSizeOf);
n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
return n;
}
// Some stack based classes for cleaning up on early return
#ifdef HAVE_PR_MEMMAP
class FileAutoCloser
{
public:
explicit FileAutoCloser(PRFileDesc* file) : mFile(file) {}
~FileAutoCloser() { PR_Close(mFile); }
private:
PRFileDesc* mFile;
};
class FileMapAutoCloser
{
public:
explicit FileMapAutoCloser(PRFileMap* map) : mMap(map) {}
~FileMapAutoCloser() { PR_CloseFileMap(mMap); }
private:
PRFileMap* mMap;
};
#else
class ANSIFileAutoCloser
{
public:
explicit ANSIFileAutoCloser(FILE* file) : mFile(file) {}
~ANSIFileAutoCloser() { fclose(mFile); }
private:
FILE* mFile;
};
#endif
JSObject*
mozJSComponentLoader::PrepareObjectForLocation(JSContext* aCx,
nsIFile* aComponentFile,
nsIURI* aURI,
bool aReuseLoaderGlobal,
bool* aRealFile)
{
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
if (aReuseLoaderGlobal) {
holder = mLoaderGlobal;
}
nsresult rv = NS_OK;
nsCOMPtr<nsIXPConnect> xpc =
do_GetService(kXPConnectServiceContractID, &rv);
NS_ENSURE_SUCCESS(rv, nullptr);
bool createdNewGlobal = false;
if (!mLoaderGlobal) {
RefPtr<BackstagePass> backstagePass;
rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
NS_ENSURE_SUCCESS(rv, nullptr);
CompartmentOptions options;
options.setZone(SystemZone)
.setVersion(JSVERSION_LATEST)
.setAddonId(aReuseLoaderGlobal ? nullptr : MapURIToAddonID(aURI));
// Defer firing OnNewGlobalObject until after the __URI__ property has
// been defined so the JS debugger can tell what module the global is
// for
rv = xpc->InitClassesWithNewWrappedGlobal(aCx,
static_cast<nsIGlobalObject*>(backstagePass),
mSystemPrincipal,
nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK,
options,
getter_AddRefs(holder));
NS_ENSURE_SUCCESS(rv, nullptr);
createdNewGlobal = true;
RootedObject global(aCx, holder->GetJSObject());
NS_ENSURE_TRUE(global, nullptr);
backstagePass->SetGlobalObject(global);
JSAutoCompartment ac(aCx, global);
if (!JS_DefineFunctions(aCx, global, gGlobalFun) ||
!JS_DefineProfilingFunctions(aCx, global)) {
return nullptr;
}
if (aReuseLoaderGlobal) {
mLoaderGlobal = holder;
}
}
RootedObject obj(aCx, holder->GetJSObject());
NS_ENSURE_TRUE(obj, nullptr);
JSAutoCompartment ac(aCx, obj);
if (aReuseLoaderGlobal) {
// If we're reusing the loader global, we don't actually use the
// global, but rather we use a different object as the 'this' object.
obj = JS_NewObject(aCx, &kFakeBackstagePassJSClass);
NS_ENSURE_TRUE(obj, nullptr);
}
*aRealFile = false;
// need to be extra careful checking for URIs pointing to files
// EnsureFile may not always get called, especially on resource URIs
// so we need to call GetFile to make sure this is a valid file
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
nsCOMPtr<nsIFile> testFile;
if (NS_SUCCEEDED(rv)) {
fileURL->GetFile(getter_AddRefs(testFile));
}
if (testFile) {
*aRealFile = true;
if (XRE_IsParentProcess()) {
RootedObject locationObj(aCx);
rv = xpc->WrapNative(aCx, obj, aComponentFile,
NS_GET_IID(nsIFile),
locationObj.address());
NS_ENSURE_SUCCESS(rv, nullptr);
NS_ENSURE_TRUE(locationObj, nullptr);
if (!JS_DefineProperty(aCx, obj, "__LOCATION__", locationObj, 0))
return nullptr;
}
}
nsAutoCString nativePath;
rv = aURI->GetSpec(nativePath);
NS_ENSURE_SUCCESS(rv, nullptr);
// Expose the URI from which the script was imported through a special
// variable that we insert into the JSM.
RootedString exposedUri(aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length()));
NS_ENSURE_TRUE(exposedUri, nullptr);
if (!JS_DefineProperty(aCx, obj, "__URI__", exposedUri, 0))
return nullptr;
if (createdNewGlobal) {
// AutoEntryScript required to invoke debugger hook, which is a
// Gecko-specific concept at present.
dom::AutoEntryScript aes(NativeGlobal(holder->GetJSObject()),
"component loader report global");
RootedObject global(aes.cx(), holder->GetJSObject());
JS_FireOnNewGlobalObject(aes.cx(), global);
}
return obj;
}
nsresult
mozJSComponentLoader::ObjectForLocation(ComponentLoaderInfo& aInfo,
nsIFile* aComponentFile,
MutableHandleObject aObject,
MutableHandleScript aTableScript,
char** aLocation,
bool aPropagateExceptions,
MutableHandleValue aException)
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
dom::AutoJSAPI jsapi;
jsapi.Init();
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
bool realFile = false;
nsresult rv = aInfo.EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
RootedObject obj(cx, PrepareObjectForLocation(cx, aComponentFile, aInfo.URI(),
mReuseLoaderGlobal, &realFile));
NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);
MOZ_ASSERT(JS_IsGlobalObject(obj) == !mReuseLoaderGlobal);
JSAutoCompartment ac(cx, obj);
RootedScript script(cx);
RootedFunction function(cx);
nsAutoCString nativePath;
rv = aInfo.URI()->GetSpec(nativePath);
NS_ENSURE_SUCCESS(rv, rv);
// Before compiling the script, first check to see if we have it in
// the startupcache. Note: as a rule, startupcache errors are not fatal
// to loading the script, since we can always slow-load.
bool writeToCache = false;
StartupCache* cache = StartupCache::GetSingleton();
nsAutoCString cachePath(kJSCachePrefix);
rv = PathifyURI(aInfo.URI(), cachePath);
NS_ENSURE_SUCCESS(rv, rv);
if (cache) {
if (!mReuseLoaderGlobal) {
rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
} else {
rv = ReadCachedFunction(cache, cachePath, cx, mSystemPrincipal,
function.address());
}
if (NS_SUCCEEDED(rv)) {
LOG(("Successfully loaded %s from startupcache\n", nativePath.get()));
} else {
// This is ok, it just means the script is not yet in the
// cache. Could mean that the cache was corrupted and got removed,
// but either way we're going to write this out.
writeToCache = true;
}
}
if (!script && !function) {
// The script wasn't in the cache , so compile it now.
LOG(("Slow loading %s\n", nativePath.get()));
// If aPropagateExceptions is true, then our caller wants us to propagate
// any exceptions out to our caller. Ensure that the engine doesn't
// eagerly report the exception.
AutoSaveContextOptions asco(cx);
if (aPropagateExceptions)
ContextOptionsRef(cx).setDontReportUncaught(true);
// Note - if mReuseLoaderGlobal is true, then we can't do lazy source,
// because we compile things as functions (rather than script), and lazy
// source isn't supported in that configuration. That's ok though,
// because we only do mReuseLoaderGlobal on b2g, where we invoke
// setDiscardSource(true) on the entire global.
CompileOptions options(cx);
options.setNoScriptRval(mReuseLoaderGlobal ? false : true)
.setVersion(JSVERSION_LATEST)
.setFileAndLine(nativePath.get(), 1)
.setSourceIsLazy(!mReuseLoaderGlobal);
if (realFile) {
#ifdef HAVE_PR_MEMMAP
int64_t fileSize;
rv = aComponentFile->GetFileSize(&fileSize);
if (NS_FAILED(rv)) {
return rv;
}
int64_t maxSize = UINT32_MAX;
if (fileSize > maxSize) {
NS_ERROR("file too large");
return NS_ERROR_FAILURE;
}
PRFileDesc* fileHandle;
rv = aComponentFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fileHandle);
if (NS_FAILED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
// Make sure the file is closed, no matter how we return.
FileAutoCloser fileCloser(fileHandle);
// We don't provide the file size here. If we did, PR_CreateFileMap
// would simply stat() the file to verify that the size we provided
// didn't require extending the file. We know that the file doesn't
// need to be extended, so skip the extra work by not providing the
// size.
PRFileMap* map = PR_CreateFileMap(fileHandle, 0, PR_PROT_READONLY);
if (!map) {
NS_ERROR("Failed to create file map");
return NS_ERROR_FAILURE;
}
// Make sure the file map is closed, no matter how we return.
FileMapAutoCloser mapCloser(map);
uint32_t fileSize32 = fileSize;
char* buf = static_cast<char*>(PR_MemMap(map, 0, fileSize32));
if (!buf) {
NS_WARNING("Failed to map file");
return NS_ERROR_FAILURE;
}
if (!mReuseLoaderGlobal) {
Compile(cx, options, buf, fileSize32, &script);
} else {
// Note: exceptions will get handled further down;
// don't early return for them here.
AutoObjectVector scopeChain(cx);
if (scopeChain.append(obj)) {
CompileFunction(cx, scopeChain,
options, nullptr, 0, nullptr,
buf, fileSize32, &function);
}
}
PR_MemUnmap(buf, fileSize32);
#else /* HAVE_PR_MEMMAP */
/**
* No memmap implementation, so fall back to
* reading in the file
*/
FILE* fileHandle;
rv = aComponentFile->OpenANSIFileDesc("r", &fileHandle);
if (NS_FAILED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
// Ensure file fclose
ANSIFileAutoCloser fileCloser(fileHandle);
int64_t len;
rv = aComponentFile->GetFileSize(&len);
if (NS_FAILED(rv) || len < 0) {
NS_WARNING("Failed to get file size");
return NS_ERROR_FAILURE;
}
char* buf = (char*) malloc(len * sizeof(char));
if (!buf) {
return NS_ERROR_FAILURE;
}
size_t rlen = fread(buf, 1, len, fileHandle);
if (rlen != (uint64_t)len) {
free(buf);
NS_WARNING("Failed to read file");
return NS_ERROR_FAILURE;
}
if (!mReuseLoaderGlobal) {
script = Compile(cx, options, buf, fileSize32);
} else {
// Note: exceptions will get handled further down;
// don't early return for them here.
AutoObjectVector scopeChain(cx);
if (scopeChain.append(obj)) {
CompileFunction(cx, scopeChain,
options, nullptr, 0, nullptr,
buf, fileSize32, &function);
}
}
free(buf);
#endif /* HAVE_PR_MEMMAP */
} else {
rv = aInfo.EnsureScriptChannel();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> scriptStream;
rv = aInfo.ScriptChannel()->Open(getter_AddRefs(scriptStream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t len64;
uint32_t bytesRead;
rv = scriptStream->Available(&len64);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
if (!len64)
return NS_ERROR_FAILURE;
uint32_t len = (uint32_t)len64;
/* malloc an internal buf the size of the file */
nsAutoArrayPtr<char> buf(new char[len + 1]);
if (!buf)
return NS_ERROR_OUT_OF_MEMORY;
/* read the file in one swoop */
rv = scriptStream->Read(buf, len, &bytesRead);
if (bytesRead != len)
return NS_BASE_STREAM_OSERROR;
buf[len] = '\0';
if (!mReuseLoaderGlobal) {
Compile(cx, options, buf, bytesRead, &script);
} else {
// Note: exceptions will get handled further down;
// don't early return for them here.
AutoObjectVector scopeChain(cx);
if (scopeChain.append(obj)) {
CompileFunction(cx, scopeChain,
options, nullptr, 0, nullptr,
buf, bytesRead, &function);
}
}
}
// Propagate the exception, if one exists. Also, don't leave the stale
// exception on this context.
if (!script && !function && aPropagateExceptions) {
JS_GetPendingException(cx, aException);
JS_ClearPendingException(cx);
}
}
if (!script && !function) {
return NS_ERROR_FAILURE;
}
// We must have a script or a function (but not both!) here. We have a
// script when we're not reusing the loader global, and a function
// otherwise.
MOZ_ASSERT(!!script != !!function);
MOZ_ASSERT(!!script == JS_IsGlobalObject(obj));
if (writeToCache) {
// We successfully compiled the script, so cache it.
if (script) {
rv = WriteCachedScript(cache, cachePath, cx, mSystemPrincipal,
script);
} else {
rv = WriteCachedFunction(cache, cachePath, cx, mSystemPrincipal,
function);
}
// Don't treat failure to write as fatal, since we might be working
// with a read-only cache.
if (NS_SUCCEEDED(rv)) {
LOG(("Successfully wrote to cache\n"));
} else {
LOG(("Failed to write to cache\n"));
}
}
// Assign aObject here so that it's available to recursive imports.
// See bug 384168.
aObject.set(obj);
RootedScript tableScript(cx, script);
if (!tableScript) {
tableScript = JS_GetFunctionScript(cx, function);
MOZ_ASSERT(tableScript);
}
aTableScript.set(tableScript);
bool ok = false;
{
// We're going to run script via JS_ExecuteScript or
// JS_CallFunction, so we need an AutoEntryScript.
// This is Gecko-specific and not in any spec.
dom::AutoEntryScript aes(NativeGlobal(CurrentGlobalOrNull(cx)),
"component loader load module");
AutoSaveContextOptions asco(cx);
if (aPropagateExceptions)
ContextOptionsRef(cx).setDontReportUncaught(true);
if (script) {
ok = JS_ExecuteScript(cx, script);
} else {
RootedValue rval(cx);
ok = JS_CallFunction(cx, obj, function, JS::HandleValueArray::empty(), &rval);
}
}
if (!ok) {
if (aPropagateExceptions) {
JS_GetPendingException(cx, aException);
JS_ClearPendingException(cx);
}
aObject.set(nullptr);
aTableScript.set(nullptr);
return NS_ERROR_FAILURE;
}
/* Freed when we remove from the table. */
*aLocation = ToNewCString(nativePath);
if (!*aLocation) {
aObject.set(nullptr);
aTableScript.set(nullptr);
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
void
mozJSComponentLoader::UnloadModules()
{
mInitialized = false;
if (mLoaderGlobal) {
MOZ_ASSERT(mReuseLoaderGlobal, "How did this happen?");
dom::AutoJSAPI jsapi;
jsapi.Init();
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
RootedObject global(cx, mLoaderGlobal->GetJSObject());
if (global) {
JSAutoCompartment ac(cx, global);
if (JS_HasExtensibleLexicalScope(global)) {
JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalScope(global));
}
JS_SetAllNonReservedSlotsToUndefined(cx, global);
} else {
NS_WARNING("Going to leak!");
}
mLoaderGlobal = nullptr;
}
mInProgressImports.Clear();
mImports.Clear();
for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
iter.Data()->Clear();
iter.Remove();
}
}
NS_IMETHODIMP
mozJSComponentLoader::Import(const nsACString& registryLocation,
HandleValue targetValArg,
JSContext* cx,
uint8_t optionalArgc,
MutableHandleValue retval)
{
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
RootedValue targetVal(cx, targetValArg);
RootedObject targetObject(cx, nullptr);
if (optionalArgc) {
// The caller passed in the optional second argument. Get it.
if (targetVal.isObject()) {
// If we're passing in something like a content DOM window, chances
// are the caller expects the properties to end up on the object
// proper and not on the Xray holder. This is dubious, but can be used
// during testing. Given that dumb callers can already leak JSMs into
// content by passing a raw content JS object (where Xrays aren't
// possible), we aim for consistency here. Waive xray.
if (WrapperFactory::IsXrayWrapper(&targetVal.toObject()) &&
!WrapperFactory::WaiveXrayAndWrap(cx, &targetVal))
{
return NS_ERROR_FAILURE;
}
targetObject = &targetVal.toObject();
} else if (!targetVal.isNull()) {
// If targetVal isNull(), we actually want to leave targetObject null.
// Not doing so breaks |make package|.
return ReportOnCaller(cx, ERROR_SCOPE_OBJ,
PromiseFlatCString(registryLocation).get());
}
} else {
nsresult rv = FindTargetObject(cx, &targetObject);
NS_ENSURE_SUCCESS(rv, rv);
}
Maybe<JSAutoCompartment> ac;
if (targetObject) {
ac.emplace(cx, targetObject);
}
RootedObject global(cx);
nsresult rv = ImportInto(registryLocation, targetObject, cx, &global);
if (global) {
if (!JS_WrapObject(cx, &global)) {
NS_ERROR("can't wrap return value");
return NS_ERROR_FAILURE;
}
retval.setObject(*global);
}
return rv;
}
NS_IMETHODIMP
mozJSComponentLoader::ImportInto(const nsACString& aLocation,
JSObject* aTargetObj,
nsAXPCNativeCallContext* cc,
JSObject** _retval)
{
JSContext* callercx;
nsresult rv = cc->GetJSContext(&callercx);
NS_ENSURE_SUCCESS(rv, rv);
RootedObject targetObject(callercx, aTargetObj);
RootedObject global(callercx);
rv = ImportInto(aLocation, targetObject, callercx, &global);
NS_ENSURE_SUCCESS(rv, rv);
*_retval = global;
return NS_OK;
}
NS_IMETHODIMP
mozJSComponentLoader::IsModuleLoaded(const nsACString& aLocation,
bool* retval)
{
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
nsresult rv;
if (!mInitialized) {
rv = ReallyInit();
NS_ENSURE_SUCCESS(rv, rv);
}
ComponentLoaderInfo info(aLocation);
rv = info.EnsureKey();
NS_ENSURE_SUCCESS(rv, rv);
*retval = !!mImports.Get(info.Key());
return NS_OK;
}
static JSObject*
ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id)
{
if (JS_HasExtensibleLexicalScope(aModObj)) {
RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj));
bool found;
if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
nsresult
mozJSComponentLoader::ImportInto(const nsACString& aLocation,
HandleObject targetObj,
JSContext* callercx,
MutableHandleObject vp)
{
vp.set(nullptr);
nsresult rv;
if (!mInitialized) {
rv = ReallyInit();
NS_ENSURE_SUCCESS(rv, rv);
}
ComponentLoaderInfo info(aLocation);
rv = info.EnsureResolvedURI();
NS_ENSURE_SUCCESS(rv, rv);
// get the JAR if there is one
nsCOMPtr<nsIJARURI> jarURI;
jarURI = do_QueryInterface(info.ResolvedURI(), &rv);
nsCOMPtr<nsIFileURL> baseFileURL;
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIURI> baseURI;
while (jarURI) {
jarURI->GetJARFile(getter_AddRefs(baseURI));
jarURI = do_QueryInterface(baseURI, &rv);
}
baseFileURL = do_QueryInterface(baseURI, &rv);
NS_ENSURE_SUCCESS(rv, rv);
} else {
baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIFile> sourceFile;
rv = baseFileURL->GetFile(getter_AddRefs(sourceFile));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> sourceLocalFile;
sourceLocalFile = do_QueryInterface(sourceFile, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = info.EnsureKey();
NS_ENSURE_SUCCESS(rv, rv);
ModuleEntry* mod;
nsAutoPtr<ModuleEntry> newEntry;
if (!mImports.Get(info.Key(), &mod) && !mInProgressImports.Get(info.Key(), &mod)) {
newEntry = new ModuleEntry(callercx);
if (!newEntry)
return NS_ERROR_OUT_OF_MEMORY;
mInProgressImports.Put(info.Key(), newEntry);
rv = info.EnsureURI();
NS_ENSURE_SUCCESS(rv, rv);
RootedValue exception(callercx);
rv = ObjectForLocation(info, sourceLocalFile, &newEntry->obj,
&newEntry->thisObjectKey,
&newEntry->location, true, &exception);
mInProgressImports.Remove(info.Key());
if (NS_FAILED(rv)) {
if (!exception.isUndefined()) {
// An exception was thrown during compilation. Propagate it
// out to our caller so they can report it.
if (!JS_WrapValue(callercx, &exception))
return NS_ERROR_OUT_OF_MEMORY;
JS_SetPendingException(callercx, exception);
return NS_OK;
}
// Something failed, but we don't know what it is, guess.
return NS_ERROR_FILE_NOT_FOUND;
}
// Set the location information for the new global, so that tools like
// about:memory may use that information
if (!mReuseLoaderGlobal) {
xpc::SetLocationForGlobal(newEntry->obj, aLocation);
}
mod = newEntry;
}
MOZ_ASSERT(mod->obj, "Import table contains entry with no object");
vp.set(mod->obj);
if (targetObj) {
// cxhelper must be created before jsapi, so that jsapi is detroyed and
// pops any context it has pushed before we report to the caller context.
JSCLContextHelper cxhelper(callercx);
// Even though we are calling JS_SetPropertyById on targetObj, we want
// to ensure that we never run script here, so we use an AutoJSAPI and
// not an AutoEntryScript.
dom::AutoJSAPI jsapi;
jsapi.Init();
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
JSAutoCompartment ac(cx, mod->obj);
RootedValue symbols(cx);
RootedObject exportedSymbolsHolder(cx, ResolveModuleObjectProperty(cx, mod->obj,
"EXPORTED_SYMBOLS"));
if (!exportedSymbolsHolder ||
!JS_GetProperty(cx, exportedSymbolsHolder,
"EXPORTED_SYMBOLS", &symbols)) {
return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT,
PromiseFlatCString(aLocation).get());
}
bool isArray;
if (!JS_IsArrayObject(cx, symbols, &isArray)) {
return NS_ERROR_FAILURE;
}
if (!isArray) {
return ReportOnCaller(cxhelper, ERROR_NOT_AN_ARRAY,
PromiseFlatCString(aLocation).get());
}
RootedObject symbolsObj(cx, &symbols.toObject());
// Iterate over symbols array, installing symbols on targetObj:
uint32_t symbolCount = 0;
if (!JS_GetArrayLength(cx, symbolsObj, &symbolCount)) {
return ReportOnCaller(cxhelper, ERROR_GETTING_ARRAY_LENGTH,
PromiseFlatCString(aLocation).get());
}
#ifdef DEBUG
nsAutoCString logBuffer;
#endif
RootedValue value(cx);
RootedId symbolId(cx);
RootedObject symbolHolder(cx);
for (uint32_t i = 0; i < symbolCount; ++i) {
if (!JS_GetElement(cx, symbolsObj, i, &value) ||
!value.isString() ||
!JS_ValueToId(cx, value, &symbolId)) {
return ReportOnCaller(cxhelper, ERROR_ARRAY_ELEMENT,
PromiseFlatCString(aLocation).get(), i);
}
symbolHolder = ResolveModuleObjectPropertyById(cx, mod->obj, symbolId);
if (!symbolHolder ||
!JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) {
JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
if (!bytes)
return NS_ERROR_FAILURE;
return ReportOnCaller(cxhelper, ERROR_GETTING_SYMBOL,
PromiseFlatCString(aLocation).get(),
bytes.ptr());
}
JSAutoCompartment target_ac(cx, targetObj);
if (!JS_WrapValue(cx, &value) ||
!JS_SetPropertyById(cx, targetObj, symbolId, value)) {
JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
if (!bytes)
return NS_ERROR_FAILURE;
return ReportOnCaller(cxhelper, ERROR_SETTING_SYMBOL,
PromiseFlatCString(aLocation).get(),
bytes.ptr());
}
#ifdef DEBUG
if (i == 0) {
logBuffer.AssignLiteral("Installing symbols [ ");
}
JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
if (!!bytes)
logBuffer.Append(bytes.ptr());
logBuffer.Append(' ');
if (i == symbolCount - 1) {
LOG(("%s] from %s\n", logBuffer.get(),
PromiseFlatCString(aLocation).get()));
}
#endif
}
}
// Cache this module for later
if (newEntry) {
mImports.Put(info.Key(), newEntry);
newEntry.forget();
}
return NS_OK;
}
NS_IMETHODIMP
mozJSComponentLoader::Unload(const nsACString & aLocation)
{
nsresult rv;
if (!mInitialized) {
return NS_OK;
}
MOZ_RELEASE_ASSERT(!mReuseLoaderGlobal, "Module unloading not supported when "
"compartment sharing is enabled");
ComponentLoaderInfo info(aLocation);
rv = info.EnsureKey();
NS_ENSURE_SUCCESS(rv, rv);
ModuleEntry* mod;
if (mImports.Get(info.Key(), &mod)) {
mImports.Remove(info.Key());
}
return NS_OK;
}
NS_IMETHODIMP
mozJSComponentLoader::Observe(nsISupports* subject, const char* topic,
const char16_t* data)
{
if (!strcmp(topic, "xpcom-shutdown-loaders")) {
UnloadModules();
} else {
NS_ERROR("Unexpected observer topic.");
}
return NS_OK;
}
size_t
mozJSComponentLoader::ModuleEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
size_t n = aMallocSizeOf(this);
n += aMallocSizeOf(location);
return n;
}
/* static */ already_AddRefed<nsIFactory>
mozJSComponentLoader::ModuleEntry::GetFactory(const mozilla::Module& module,
const mozilla::Module::CIDEntry& entry)
{
const ModuleEntry& self = static_cast<const ModuleEntry&>(module);
MOZ_ASSERT(self.getfactoryobj, "Handing out an uninitialized module?");
nsCOMPtr<nsIFactory> f;
nsresult rv = self.getfactoryobj->Get(*entry.cid, getter_AddRefs(f));
if (NS_FAILED(rv))
return nullptr;
return f.forget();
}
//----------------------------------------------------------------------
JSCLContextHelper::JSCLContextHelper(JSContext* aCx)
: mContext(aCx)
, mBuf(nullptr)
{
}
JSCLContextHelper::~JSCLContextHelper()
{
if (mBuf) {
JS_ReportError(mContext, mBuf);
JS_smprintf_free(mBuf);
}
}
void
JSCLContextHelper::reportErrorAfterPop(char* buf)
{
MOZ_ASSERT(!mBuf, "Already called reportErrorAfterPop");
mBuf = buf;
}