Backed out changeset 7d06b68c44d0 (bug 1079335) Backed out changeset 92030169528e (bug 1079301) Backed out changeset c09d7f95554a (bug 1047483) Backed out changeset c199f1057d7e (bug 1047483) Backed out changeset 18830d07884c (bug 1047483) Backed out changeset e087289ccfbb (bug 1047483) Backed out changeset 6238ff5d3ed0 (bug 1047483) CLOSED TREE
1442 lines
44 KiB
C++
1442 lines
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"
|
|
|
|
#ifdef MOZ_LOGGING
|
|
#define FORCE_PR_LOG
|
|
#endif
|
|
|
|
#include <cstdarg>
|
|
|
|
#include "prlog.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 "nsDOMBlobBuilder.h"
|
|
#include "jsprf.h"
|
|
#include "nsJSPrincipals.h"
|
|
#include "nsJSUtils.h"
|
|
#include "xpcprivate.h"
|
|
#include "xpcpublic.h"
|
|
#include "nsContentUtils.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"
|
|
|
|
#include "js/OldDebugAPI.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",
|
|
0,
|
|
JS_PropertyStub,
|
|
JS_DeletePropertyStub,
|
|
JS_PropertyStub,
|
|
JS_StrictPropertyStub,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub
|
|
};
|
|
|
|
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)
|
|
|
|
#ifdef PR_LOGGING
|
|
// NSPR_LOG_MODULES=JSComponentLoader:5
|
|
static PRLogModuleInfo *gJSCLLog;
|
|
#endif
|
|
|
|
#define LOG(args) PR_LOG(gJSCLLog, PR_LOG_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, jsval *vp)
|
|
{
|
|
#ifdef DEBUG
|
|
return Dump(cx, argc, vp);
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
File(JSContext *cx, unsigned argc, Value *vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() == 0) {
|
|
XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> native;
|
|
nsresult rv = DOMMultipartFileImpl::NewFile(getter_AddRefs(native));
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIJSNativeInitializer> initializer = do_QueryInterface(native);
|
|
MOZ_ASSERT(initializer);
|
|
|
|
rv = initializer->Initialize(nullptr, cx, nullptr, args);
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
|
|
nsXPConnect *xpc = nsXPConnect::XPConnect();
|
|
JSObject *glob = CurrentGlobalOrNull(cx);
|
|
|
|
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
|
|
rv = xpc->WrapNativeToJSVal(cx, glob, native, nullptr,
|
|
&NS_GET_IID(nsISupports),
|
|
true, args.rval());
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Blob(JSContext *cx, unsigned argc, Value *vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
nsCOMPtr<nsISupports> native;
|
|
nsresult rv = DOMMultipartFileImpl::NewBlob(getter_AddRefs(native));
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIJSNativeInitializer> initializer = do_QueryInterface(native);
|
|
MOZ_ASSERT(initializer);
|
|
|
|
rv = initializer->Initialize(nullptr, cx, nullptr, args);
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
|
|
nsXPConnect *xpc = nsXPConnect::XPConnect();
|
|
JSObject *glob = CurrentGlobalOrNull(cx);
|
|
|
|
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
|
|
rv = xpc->WrapNativeToJSVal(cx, glob, native, nullptr,
|
|
&NS_GET_IID(nsISupports),
|
|
true, args.rval());
|
|
if (NS_FAILED(rv)) {
|
|
XPCThrower::Throw(rv, cx);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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("File", File, 1,JSFUN_CONSTRUCTOR),
|
|
JS_FS("Blob", Blob, 2,JSFUN_CONSTRUCTOR),
|
|
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 &) MOZ_DELETE;
|
|
const JSCLContextHelper& operator=(const JSCLContextHelper &) MOZ_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");
|
|
|
|
#ifdef PR_LOGGING
|
|
if (!gJSCLLog) {
|
|
gJSCLLog = PR_NewLogModule("JSComponentLoader");
|
|
}
|
|
#endif
|
|
|
|
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 mIOService->NewChannelFromURI(mURI, getter_AddRefs(mScriptChannel));
|
|
}
|
|
|
|
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(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;
|
|
}
|
|
|
|
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();
|
|
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);
|
|
|
|
nsCOMPtr<nsIXPConnectJSObjectHolder> cm_holder;
|
|
rv = xpc->WrapNative(cx, entry->obj, cm,
|
|
NS_GET_IID(nsIComponentManager),
|
|
getter_AddRefs(cm_holder));
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSObject* cm_jsobj = cm_holder->GetJSObject();
|
|
if (!cm_jsobj) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIXPConnectJSObjectHolder> file_holder;
|
|
RootedObject entryObj(cx, entry->obj);
|
|
rv = xpc->WrapNative(cx, entryObj, file,
|
|
NS_GET_IID(nsIFile),
|
|
getter_AddRefs(file_holder));
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSObject* file_jsobj = file_holder->GetJSObject();
|
|
if (!file_jsobj) {
|
|
return nullptr;
|
|
}
|
|
|
|
RootedValue NSGetFactory_val(cx);
|
|
if (!JS_GetProperty(cx, entryObj, "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::GetObjectParent(JS_GetFunctionObject(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;
|
|
}
|
|
|
|
/* static */ size_t
|
|
mozJSComponentLoader::DataEntrySizeOfExcludingThis(const nsACString& aKey,
|
|
ModuleEntry* const& aData,
|
|
MallocSizeOf aMallocSizeOf, void*)
|
|
{
|
|
return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
|
|
aData->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
/* static */ size_t
|
|
mozJSComponentLoader::ClassEntrySizeOfExcludingThis(const nsACString& aKey,
|
|
const nsAutoPtr<ModuleEntry>& aData,
|
|
MallocSizeOf aMallocSizeOf, void*)
|
|
{
|
|
return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
|
|
aData->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
size_t
|
|
mozJSComponentLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
|
|
{
|
|
size_t amount = aMallocSizeOf(this);
|
|
|
|
amount += mModules.SizeOfExcludingThis(DataEntrySizeOfExcludingThis, aMallocSizeOf);
|
|
amount += mImports.SizeOfExcludingThis(ClassEntrySizeOfExcludingThis, aMallocSizeOf);
|
|
amount += mInProgressImports.SizeOfExcludingThis(DataEntrySizeOfExcludingThis, aMallocSizeOf);
|
|
|
|
return amount;
|
|
}
|
|
|
|
// 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) {
|
|
nsRefPtr<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, NullPtr(), NullPtr());
|
|
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;
|
|
|
|
nsCOMPtr<nsIXPConnectJSObjectHolder> locationHolder;
|
|
rv = xpc->WrapNative(aCx, obj, aComponentFile,
|
|
NS_GET_IID(nsIFile),
|
|
getter_AddRefs(locationHolder));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
RootedObject locationObj(aCx, locationHolder->GetJSObject());
|
|
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()));
|
|
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();
|
|
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);
|
|
|
|
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, obj, options, buf, fileSize32, &script);
|
|
} else {
|
|
CompileFunction(cx, obj, 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, obj, options, buf,
|
|
fileSize32);
|
|
} else {
|
|
function = CompileFunction(cx, obj, options,
|
|
nullptr, 0, nullptr,
|
|
buf, fileSize32);
|
|
}
|
|
|
|
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, obj, options, buf, bytesRead, &script);
|
|
} else {
|
|
CompileFunction(cx, obj, 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;
|
|
}
|
|
|
|
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_ExecuteScriptVersion or
|
|
// JS_CallFunction, so we need an AutoEntryScript.
|
|
// This is Gecko-specific and not in any spec.
|
|
dom::AutoEntryScript aes(NativeGlobal(CurrentGlobalOrNull(cx)));
|
|
AutoSaveContextOptions asco(cx);
|
|
if (aPropagateExceptions)
|
|
ContextOptionsRef(cx).setDontReportUncaught(true);
|
|
if (script) {
|
|
ok = JS_ExecuteScriptVersion(cx, obj, script, JSVERSION_LATEST);
|
|
} 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;
|
|
}
|
|
|
|
/* static */ PLDHashOperator
|
|
mozJSComponentLoader::ClearModules(const nsACString& key, ModuleEntry*& entry, void* cx)
|
|
{
|
|
entry->Clear();
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
void
|
|
mozJSComponentLoader::UnloadModules()
|
|
{
|
|
mInitialized = false;
|
|
|
|
if (mLoaderGlobal) {
|
|
MOZ_ASSERT(mReuseLoaderGlobal, "How did this happen?");
|
|
|
|
dom::AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext* cx = jsapi.cx();
|
|
RootedObject global(cx, mLoaderGlobal->GetJSObject());
|
|
if (global) {
|
|
JSAutoCompartment ac(cx, global);
|
|
JS_SetAllNonReservedSlotsToUndefined(cx, global);
|
|
} else {
|
|
NS_WARNING("Going to leak!");
|
|
}
|
|
|
|
mLoaderGlobal = nullptr;
|
|
}
|
|
|
|
mInProgressImports.Clear();
|
|
mImports.Clear();
|
|
|
|
mModules.Enumerate(ClearModules, nullptr);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* [noscript] JSObjectPtr importInto(in AUTF8String registryLocation,
|
|
in JSObjectPtr targetObj); */
|
|
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;
|
|
}
|
|
|
|
/* boolean isModuleLoaded (in AUTF8String registryLocation); */
|
|
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;
|
|
}
|
|
|
|
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();
|
|
JSContext* cx = jsapi.cx();
|
|
JSAutoCompartment ac(cx, mod->obj);
|
|
|
|
RootedValue symbols(cx);
|
|
RootedObject modObj(cx, mod->obj);
|
|
if (!JS_GetProperty(cx, modObj,
|
|
"EXPORTED_SYMBOLS", &symbols)) {
|
|
return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT,
|
|
PromiseFlatCString(aLocation).get());
|
|
}
|
|
|
|
if (!JS_IsArrayObject(cx, symbols)) {
|
|
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);
|
|
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);
|
|
}
|
|
|
|
RootedObject modObj(cx, mod->obj);
|
|
if (!JS_GetPropertyById(cx, modObj, 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;
|
|
}
|