diff -r 000000000000 -r 6474c204b198 js/xpconnect/loader/mozJSComponentLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1470 @@ +/* -*- 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 + +#include "prlog.h" +#ifdef ANDROID +#include +#endif +#ifdef XP_WIN +#include +#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 "nsIJSRuntimeService.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 "xpcprivate.h" +#include "xpcpublic.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "WrapperFactory.h" + +#include "mozilla/scache/StartupCache.h" +#include "mozilla/scache/StartupCacheUtils.h" +#include "mozilla/Preferences.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 kJSRuntimeServiceContractID[] = "@mozilla.org/js/xpc/RuntimeService;1"; +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; + + JSString *str = JS::ToString(cx, args[0]); + if (!str) + return false; + + size_t length; + const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); + if (!chars) + return false; + + NS_ConvertUTF16toUTF8 utf8str(reinterpret_cast(chars), + length); +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get()); +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + OutputDebugStringW(reinterpret_cast(chars)); + } +#endif + fputs(utf8str.get(), 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 native; + nsresult rv = nsDOMMultipartFile::NewFile(getter_AddRefs(native)); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + + nsCOMPtr 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 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 native; + nsresult rv = nsDOMMultipartFile::NewBlob(getter_AddRefs(native)); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + + nsCOMPtr 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 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: + JSCLContextHelper(JSContext* aCx); + ~JSCLContextHelper(); + + void reportErrorAfterPop(char *buf); + + operator JSContext*() const {return mContext;} + +private: + + JSContext* mContext; + nsCxPusher mPusher; + char* mBuf; + + // prevent copying and assignment + JSCLContextHelper(const JSCLContextHelper &) MOZ_DELETE; + const JSCLContextHelper& operator=(const JSCLContextHelper &) MOZ_DELETE; +}; + + +class JSCLAutoErrorReporterSetter +{ +public: + JSCLAutoErrorReporterSetter(JSContext* cx, JSErrorReporter reporter) + {mContext = cx; mOldReporter = JS_SetErrorReporter(cx, reporter);} + ~JSCLAutoErrorReporterSetter() + {JS_SetErrorReporter(mContext, mOldReporter);} +private: + JSContext* mContext; + JSErrorReporter mOldReporter; + + JSCLAutoErrorReporterSetter(const JSCLAutoErrorReporterSetter &) MOZ_DELETE; + const JSCLAutoErrorReporterSetter& operator=(const JSCLAutoErrorReporterSetter &) 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() + : mRuntime(nullptr), + mContext(nullptr), + mModules(32), + mImports(32), + mInProgressImports(32), + mThisObjects(32), + mInitialized(false), + mReuseLoaderGlobal(false) +{ + MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton"); + +#ifdef PR_LOGGING + if (!gJSCLLog) { + gJSCLLog = PR_NewLogModule("JSComponentLoader"); + } +#endif + + sSelf = this; +} + +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. +#ifdef MOZ_B2G + mReuseLoaderGlobal = true; +#endif + + /* + * Get the JSRuntime from the runtime svc, if possible. + * We keep a reference around, because it's a Bad Thing if the runtime + * service gets shut down before we're done. Bad! + */ + + mRuntimeService = do_GetService(kJSRuntimeServiceContractID, &rv); + if (NS_FAILED(rv) || + NS_FAILED(rv = mRuntimeService->GetRuntime(&mRuntime))) + return rv; + + // Create our compilation context. + mContext = JS_NewContext(mRuntime, 256); + if (!mContext) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr 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 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 file = aFile.GetBaseFile(); + + nsCString spec; + aFile.GetURIString(spec); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), spec); + if (NS_FAILED(rv)) + return nullptr; + + if (!mInitialized) { + rv = ReallyInit(); + if (NS_FAILED(rv)) + return nullptr; + } + + ModuleEntry* mod; + if (mModules.Get(spec, &mod)) + return mod; + + nsAutoPtr entry(new ModuleEntry(mContext)); + + JSAutoRequest ar(mContext); + RootedValue dummy(mContext); + rv = ObjectForLocation(file, uri, &entry->obj, &entry->thisObjectKey, + &entry->location, false, &dummy); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, + &rv); + if (NS_FAILED(rv)) + return nullptr; + + nsCOMPtr cm; + rv = NS_GetComponentManager(getter_AddRefs(cm)); + if (NS_FAILED(rv)) + return nullptr; + + JSCLContextHelper cx(mContext); + JSAutoCompartment ac(cx, entry->obj); + + nsCOMPtr 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 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; + } + + JSCLAutoErrorReporterSetter aers(cx, xpc::SystemErrorReporter); + + RootedValue NSGetFactory_val(cx); + if (!JS_GetProperty(cx, entryObj, "NSGetFactory", &NSGetFactory_val) || + JSVAL_IS_VOID(NSGetFactory_val)) { + return nullptr; + } + + if (JS_TypeOfValue(cx, NSGetFactory_val) != JSTYPE_FUNCTION) { + nsAutoCString spec; + uri->GetSpec(spec); + 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) { + JSScript* script = + js::GetOutermostEnclosingFunctionOfScriptedCaller(aCx); + if (script) { + targetObject = mThisObjects.Get(script); + } + } + + // 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. + nsresult rv; + nsCOMPtr xpc = + do_GetService(kXPConnectServiceContractID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAXPCNativeCallContext *cc = nullptr; + rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr wn; + rv = cc->GetCalleeWrapper(getter_AddRefs(wn)); + NS_ENSURE_SUCCESS(rv, rv); + + targetObject = wn->GetJSObject(); + if (!targetObject) { + NS_ERROR("null calling object"); + return NS_ERROR_FAILURE; + } + + targetObject = JS_GetGlobalForObject(aCx, targetObject); + } + + aTargetObject.set(targetObject); + return NS_OK; +} + +void +mozJSComponentLoader::NoteSubScript(HandleScript aScript, HandleObject aThisObject) +{ + if (!mInitialized && NS_FAILED(ReallyInit())) { + MOZ_CRASH(); + } + + if (js::GetObjectJSClass(aThisObject) == &kFakeBackstagePassJSClass) { + mThisObjects.Put(aScript, aThisObject); + } +} + +/* 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& 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); + amount += mThisObjects.SizeOfExcludingThis(nullptr, 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(JSCLContextHelper& aCx, + nsIFile *aComponentFile, + nsIURI *aURI, + bool aReuseLoaderGlobal, + bool *aRealFile) +{ + nsCOMPtr holder; + if (aReuseLoaderGlobal) { + holder = mLoaderGlobal; + } + + nsresult rv = NS_OK; + nsCOMPtr xpc = + do_GetService(kXPConnectServiceContractID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + bool createdNewGlobal = false; + + if (!mLoaderGlobal) { + nsRefPtr backstagePass; + rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); + NS_ENSURE_SUCCESS(rv, nullptr); + + CompartmentOptions options; + options.setZone(SystemZone) + .setVersion(JSVERSION_LATEST); + // 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(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 fileURL = do_QueryInterface(aURI, &rv); + nsCOMPtr testFile; + if (NS_SUCCEEDED(rv)) { + fileURL->GetFile(getter_AddRefs(testFile)); + } + + if (testFile) { + *aRealFile = true; + + nsCOMPtr 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) { + RootedObject global(aCx, holder->GetJSObject()); + JS_FireOnNewGlobalObject(aCx, global); + } + + return obj; +} + +nsresult +mozJSComponentLoader::ObjectForLocation(nsIFile *aComponentFile, + nsIURI *aURI, + MutableHandleObject aObject, + MutableHandleScript aTableScript, + char **aLocation, + bool aPropagateExceptions, + MutableHandleValue aException) +{ + JSCLContextHelper cx(mContext); + + JS_AbortIfWrongThread(JS_GetRuntime(cx)); + + JSCLAutoErrorReporterSetter aers(cx, xpc::SystemErrorReporter); + + bool realFile = false; + RootedObject obj(cx, PrepareObjectForLocation(cx, aComponentFile, aURI, + mReuseLoaderGlobal, &realFile)); + NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE); + + JSAutoCompartment ac(cx, obj); + + RootedScript script(cx); + RootedFunction function(cx); + + nsAutoCString nativePath; + nsresult rv = aURI->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(aURI, 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(PR_MemMap(map, 0, fileSize32)); + if (!buf) { + NS_WARNING("Failed to map 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); + } + + 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 { + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr scriptChannel; + rv = ioService->NewChannelFromURI(aURI, getter_AddRefs(scriptChannel)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr scriptStream; + rv = 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 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) { + script = Compile(cx, obj, options, buf, bytesRead); + } else { + function = CompileFunction(cx, obj, options, + nullptr, 0, nullptr, + buf, bytesRead); + } + } + // 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); + + if (js::GetObjectJSClass(obj) == &kFakeBackstagePassJSClass) { + MOZ_ASSERT(mReuseLoaderGlobal); + // tableScript stays in the table until shutdown. It is rooted by + // virtue of the fact that aTableScript is a handle to + // ModuleEntry::thisObjectKey, which is a PersistentRootedScript. Since + // ModuleEntries are never dynamically unloaded when mReuseLoaderGlobal + // is true, this prevents it from being collected and another script + // getting the same address. + mThisObjects.Put(tableScript, obj); + } + bool ok = false; + + { + 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); + mThisObjects.Remove(tableScript); + return NS_ERROR_FAILURE; + } + + /* Freed when we remove from the table. */ + *aLocation = ToNewCString(nativePath); + if (!*aLocation) { + aObject.set(nullptr); + aTableScript.set(nullptr); + mThisObjects.Remove(tableScript); + 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?"); + + JSAutoRequest ar(mContext); + RootedObject global(mContext, mLoaderGlobal->GetJSObject()); + if (global) { + JSAutoCompartment ac(mContext, global); + JS_SetAllNonReservedSlotsToUndefined(mContext, global); + } else { + NS_WARNING("Going to leak!"); + } + + mLoaderGlobal = nullptr; + } + + mInProgressImports.Clear(); + mImports.Clear(); + mThisObjects.Clear(); + + mModules.Enumerate(ClearModules, nullptr); + + JS_DestroyContextNoGC(mContext); + mContext = nullptr; + + mRuntimeService = 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 ac; + if (targetObject) { + ac.construct(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; +} + +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); + } + + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the URI. + nsCOMPtr resURI; + rv = ioService->NewURI(aLocation, nullptr, nullptr, getter_AddRefs(resURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // figure out the resolved URI + nsCOMPtr scriptChannel; + rv = ioService->NewChannelFromURI(resURI, getter_AddRefs(scriptChannel)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + nsCOMPtr resolvedURI; + rv = scriptChannel->GetURI(getter_AddRefs(resolvedURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // get the JAR if there is one + nsCOMPtr jarURI; + jarURI = do_QueryInterface(resolvedURI, &rv); + nsCOMPtr baseFileURL; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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(resolvedURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr sourceFile; + rv = baseFileURL->GetFile(getter_AddRefs(sourceFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr sourceLocalFile; + sourceLocalFile = do_QueryInterface(sourceFile, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString key; + rv = resolvedURI->GetSpec(key); + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + nsAutoPtr newEntry; + if (!mImports.Get(key, &mod) && !mInProgressImports.Get(key, &mod)) { + newEntry = new ModuleEntry(callercx); + if (!newEntry) + return NS_ERROR_OUT_OF_MEMORY; + mInProgressImports.Put(key, newEntry); + + RootedValue exception(callercx); + rv = ObjectForLocation(sourceLocalFile, resURI, &newEntry->obj, + &newEntry->thisObjectKey, + &newEntry->location, true, &exception); + + mInProgressImports.Remove(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) { + JSCLContextHelper cxhelper(mContext); + JSAutoCompartment ac(mContext, mod->obj); + + RootedValue symbols(mContext); + RootedObject modObj(mContext, mod->obj); + if (!JS_GetProperty(mContext, modObj, + "EXPORTED_SYMBOLS", &symbols)) { + return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT, + PromiseFlatCString(aLocation).get()); + } + + if (!JS_IsArrayObject(mContext, symbols)) { + return ReportOnCaller(cxhelper, ERROR_NOT_AN_ARRAY, + PromiseFlatCString(aLocation).get()); + } + + RootedObject symbolsObj(mContext, &symbols.toObject()); + + // Iterate over symbols array, installing symbols on targetObj: + + uint32_t symbolCount = 0; + if (!JS_GetArrayLength(mContext, symbolsObj, &symbolCount)) { + return ReportOnCaller(cxhelper, ERROR_GETTING_ARRAY_LENGTH, + PromiseFlatCString(aLocation).get()); + } + +#ifdef DEBUG + nsAutoCString logBuffer; +#endif + + RootedValue value(mContext); + RootedId symbolId(mContext); + for (uint32_t i = 0; i < symbolCount; ++i) { + if (!JS_GetElement(mContext, symbolsObj, i, &value) || + !value.isString() || + !JS_ValueToId(mContext, value, &symbolId)) { + return ReportOnCaller(cxhelper, ERROR_ARRAY_ELEMENT, + PromiseFlatCString(aLocation).get(), i); + } + + RootedObject modObj(mContext, mod->obj); + if (!JS_GetPropertyById(mContext, modObj, symbolId, &value)) { + JSAutoByteString bytes(mContext, JSID_TO_STRING(symbolId)); + if (!bytes) + return NS_ERROR_FAILURE; + return ReportOnCaller(cxhelper, ERROR_GETTING_SYMBOL, + PromiseFlatCString(aLocation).get(), + bytes.ptr()); + } + + JSAutoCompartment target_ac(mContext, targetObj); + + if (!JS_WrapValue(mContext, &value) || + !JS_SetPropertyById(mContext, targetObj, symbolId, value)) { + JSAutoByteString bytes(mContext, 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(mContext, JSID_TO_STRING(symbolId)); + if (!!bytes) + logBuffer.Append(bytes.ptr()); + logBuffer.AppendLiteral(" "); + if (i == symbolCount - 1) { + LOG(("%s] from %s\n", logBuffer.get(), + PromiseFlatCString(aLocation).get())); + } +#endif + } + } + + // Cache this module for later + if (newEntry) { + mImports.Put(key, newEntry); + newEntry.forget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozJSComponentLoader::Unload(const nsACString & aLocation) +{ + nsresult rv; + + if (!mInitialized) { + return NS_OK; + } + + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the URI. + nsCOMPtr resURI; + rv = ioService->NewURI(aLocation, nullptr, nullptr, getter_AddRefs(resURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // figure out the resolved URI + nsCOMPtr scriptChannel; + rv = ioService->NewChannelFromURI(resURI, getter_AddRefs(scriptChannel)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + nsCOMPtr resolvedURI; + rv = scriptChannel->GetURI(getter_AddRefs(resolvedURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString key; + rv = resolvedURI->GetSpec(key); + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + if (mImports.Get(key, &mod)) { + mImports.Remove(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 +mozJSComponentLoader::ModuleEntry::GetFactory(const mozilla::Module& module, + const mozilla::Module::CIDEntry& entry) +{ + const ModuleEntry& self = static_cast(module); + MOZ_ASSERT(self.getfactoryobj, "Handing out an uninitialized module?"); + + nsCOMPtr 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) +{ + mPusher.Push(mContext); + JS_BeginRequest(mContext); +} + +JSCLContextHelper::~JSCLContextHelper() +{ + JS_EndRequest(mContext); + mPusher.Pop(); + JSContext *restoredCx = nsContentUtils::GetCurrentJSContext(); + if (restoredCx && mBuf) { + JS_ReportError(restoredCx, mBuf); + } + + if (mBuf) { + JS_smprintf_free(mBuf); + } +} + +void +JSCLContextHelper::reportErrorAfterPop(char *buf) +{ + MOZ_ASSERT(!mBuf, "Already called reportErrorAfterPop"); + mBuf = buf; +}