/* -*- 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 #include "mozilla/Logging.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 "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, 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 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 mIOService; nsCOMPtr mURI; nsCOMPtr mScriptChannel; nsCOMPtr mResolvedURI; Maybe 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 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); 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 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 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; JSAutoCompartment ac(cx, entry->obj); RootedObject entryObj(cx, entry->obj); 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::GetObjectEnvironmentObjectForFunction(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& 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 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) .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(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 fileURL = do_QueryInterface(aURI, &rv); nsCOMPtr testFile; if (NS_SUCCEEDED(rv)) { fileURL->GetFile(getter_AddRefs(testFile)); } if (testFile) { *aRealFile = true; if (XRE_GetProcessType() == GeckoProcessType_Default) { 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) { // 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(); 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(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 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 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; } /* 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 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 jarURI; jarURI = do_QueryInterface(info.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(info.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); rv = info.EnsureKey(); NS_ENSURE_SUCCESS(rv, rv); ModuleEntry* mod; nsAutoPtr 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 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) { } 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; }