/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=2 sw=4 et tw=80: * * 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/. */ /* XPConnect JavaScript interactive shell. */ #include #include "mozilla/Util.h" #include "jsapi.h" #include "jsdbgapi.h" #include "jsfriendapi.h" #include "jsprf.h" #include "nsXULAppAPI.h" #include "nsServiceManagerUtils.h" #include "nsComponentManagerUtils.h" #include "nsStringAPI.h" #include "nsIXPConnect.h" #include "nsIXPCScriptable.h" #include "nsIInterfaceInfo.h" #include "nsIInterfaceInfoManager.h" #include "nsIJSNativeInitializer.h" #include "nsIXPCScriptable.h" #include "nsIServiceManager.h" #include "nsIComponentManager.h" #include "nsIComponentRegistrar.h" #include "nsIFile.h" #include "nsStringAPI.h" #include "nsIDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nscore.h" #include "nsArrayEnumerator.h" #include "nsCOMArray.h" #include "nsDirectoryServiceUtils.h" #include "nsMemory.h" #include "nsISupportsImpl.h" #include "nsIJSRuntimeService.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "nsIXPCSecurityManager.h" #include "nsJSPrincipals.h" #include "xpcpublic.h" #include "nsXULAppAPI.h" #include "BackstagePass.h" #include "nsCxPusher.h" #ifdef XP_MACOSX #include "xpcshellMacUtils.h" #endif #ifdef XP_WIN #include #include // we want a wmain entry point #define XRE_DONT_PROTECT_DLL_LOAD #define XRE_WANT_ENVIRON #include "nsWindowsWMain.cpp" #define snprintf _snprintf #define strcasecmp _stricmp #endif #ifdef ANDROID #include #endif #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" // all this crap is needed to do the interactive shell stuff #include #include #ifdef HAVE_IO_H #include /* for isatty() */ #endif #ifdef HAVE_UNISTD_H #include /* for isatty() */ #endif #ifdef MOZ_CRASHREPORTER #include "nsICrashReporter.h" #endif using namespace mozilla; using namespace JS; class XPCShellDirProvider : public nsIDirectoryServiceProvider2 { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIDIRECTORYSERVICEPROVIDER NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 XPCShellDirProvider() { } ~XPCShellDirProvider() { } // The platform resource folder bool SetGREDir(const char *dir); void ClearGREDir() { mGREDir = nullptr; } // The application resource folder void SetAppDir(nsIFile *appFile); void ClearAppDir() { mAppDir = nullptr; } // The app executable void SetAppFile(nsIFile *appFile); void ClearAppFile() { mAppFile = nullptr; } // An additional custom plugin dir if specified void SetPluginDir(nsIFile* pluginDir); void ClearPluginDir() { mPluginDir = nullptr; } private: nsCOMPtr mGREDir; nsCOMPtr mAppDir; nsCOMPtr mPluginDir; nsCOMPtr mAppFile; }; /***************************************************************************/ #ifdef JS_THREADSAFE #define DoBeginRequest(cx) JS_BeginRequest((cx)) #define DoEndRequest(cx) JS_EndRequest((cx)) #else #define DoBeginRequest(cx) ((void)0) #define DoEndRequest(cx) ((void)0) #endif /***************************************************************************/ static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; #define EXITCODE_RUNTIME_ERROR 3 #define EXITCODE_FILE_NOT_FOUND 4 FILE *gOutFile = NULL; FILE *gErrFile = NULL; FILE *gInFile = NULL; int gExitCode = 0; bool gIgnoreReportedErrors = false; JSBool gQuitting = false; static JSBool reportWarnings = true; static JSBool compileOnly = false; JSPrincipals *gJSPrincipals = nullptr; nsAutoString *gWorkingDirectory = nullptr; static JSBool GetLocationProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) { #if !defined(XP_WIN) && !defined(XP_UNIX) //XXX: your platform should really implement this return false; #else JSScript *script; JS_DescribeScriptedCaller(cx, &script, NULL); const char *filename = JS_GetScriptFilename(cx, script); if (filename) { nsresult rv; nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, &rv); #if defined(XP_WIN) // convert from the system codepage to UTF-16 int bufferSize = MultiByteToWideChar(CP_ACP, 0, filename, -1, NULL, 0); nsAutoString filenameString; filenameString.SetLength(bufferSize); MultiByteToWideChar(CP_ACP, 0, filename, -1, (LPWSTR)filenameString.BeginWriting(), filenameString.Length()); // remove the null terminator filenameString.SetLength(bufferSize - 1); // replace forward slashes with backslashes, // since nsLocalFileWin chokes on them PRUnichar *start, *end; filenameString.BeginWriting(&start, &end); while (start != end) { if (*start == L'/') *start = L'\\'; start++; } #elif defined(XP_UNIX) NS_ConvertUTF8toUTF16 filenameString(filename); #endif nsCOMPtr location; if (NS_SUCCEEDED(rv)) { rv = NS_NewLocalFile(filenameString, false, getter_AddRefs(location)); } if (!location && gWorkingDirectory) { // could be a relative path, try appending it to the cwd // and then normalize nsAutoString absolutePath(*gWorkingDirectory); absolutePath.Append(filenameString); rv = NS_NewLocalFile(absolutePath, false, getter_AddRefs(location)); } if (location) { nsCOMPtr locationHolder; bool symlink; // don't normalize symlinks, because that's kind of confusing if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && !symlink) location->Normalize(); rv = xpc->WrapNative(cx, obj, location, NS_GET_IID(nsIFile), getter_AddRefs(locationHolder)); if (NS_SUCCEEDED(rv) && locationHolder->GetJSObject()) { vp.set(OBJECT_TO_JSVAL(locationHolder->GetJSObject())); } } } return true; #endif } #ifdef EDITLINE extern "C" { extern JS_EXPORT_API(char *) readline(const char *prompt); extern JS_EXPORT_API(void) add_history(char *line); } #endif static JSBool GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive? */ if (file == stdin) { char *linep = readline(prompt); if (!linep) return false; if (*linep) add_history(linep); strcpy(bufp, linep); JS_free(cx, linep); bufp += strlen(bufp); *bufp++ = '\n'; *bufp = '\0'; } else #endif { char line[256] = { '\0' }; fputs(prompt, gOutFile); fflush(gOutFile); if ((!fgets(line, sizeof line, file) && errno != EINTR) || feof(file)) return false; strcpy(bufp, line); } return true; } static JSBool ReadLine(JSContext *cx, unsigned argc, jsval *vp) { // While 4096 might be quite arbitrary, this is something to be fixed in // bug 105707. It is also the same limit as in ProcessFile. char buf[4096]; JSString *str; /* If a prompt was specified, construct the string */ if (argc > 0) { str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]); if (!str) return false; } else { str = JSVAL_TO_STRING(JS_GetEmptyStringValue(cx)); } /* Get a line from the infile */ JSAutoByteString strBytes(cx, str); if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.ptr())) return false; /* Strip newline character added by GetLine() */ unsigned int buflen = strlen(buf); if (buflen == 0) { if (feof(gInFile)) { JS_SET_RVAL(cx, vp, JSVAL_NULL); return true; } } else if (buf[buflen - 1] == '\n') { --buflen; } /* Turn buf into a JSString */ str = JS_NewStringCopyN(cx, buf, buflen); if (!str) return false; JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str)); return true; } static JSBool Print(JSContext *cx, unsigned argc, jsval *vp) { unsigned i, n; JSString *str; jsval *argv = JS_ARGV(cx, vp); for (i = n = 0; i < argc; i++) { str = JS_ValueToString(cx, argv[i]); if (!str) return false; JSAutoByteString strBytes(cx, str); if (!strBytes) return false; fprintf(gOutFile, "%s%s", i ? " " : "", strBytes.ptr()); fflush(gOutFile); } n++; if (n) fputc('\n', gOutFile); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static JSBool Dump(JSContext *cx, unsigned argc, jsval *vp) { JS_SET_RVAL(cx, vp, JSVAL_VOID); JSString *str; if (!argc) return true; str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]); if (!str) return false; JSAutoByteString bytes(cx, str); if (!bytes) return false; #ifdef ANDROID __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", bytes.ptr()); #endif fputs(bytes.ptr(), gOutFile); fflush(gOutFile); return true; } static JSBool Load(JSContext *cx, unsigned argc, jsval *vp) { JS::Rooted obj(cx, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; jsval *argv = JS_ARGV(cx, vp); for (unsigned i = 0; i < argc; i++) { JSString *str = JS_ValueToString(cx, argv[i]); if (!str) return false; argv[i] = STRING_TO_JSVAL(str); JSAutoByteString filename(cx, str); if (!filename) return false; FILE *file = fopen(filename.ptr(), "r"); if (!file) { JS_ReportError(cx, "cannot open file '%s' for reading", filename.ptr()); return false; } JS::CompileOptions options(cx); options.setUTF8(true) .setFileAndLine(filename.ptr(), 1) .setPrincipals(gJSPrincipals); JS::RootedObject rootedObj(cx, obj); JSScript *script = JS::Compile(cx, rootedObj, options, file); fclose(file); if (!script) return false; JS::Rooted result(cx); if (!compileOnly && !JS_ExecuteScript(cx, obj, script, result.address())) return false; } JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static JSBool Version(JSContext *cx, unsigned argc, jsval *vp) { JSVersion origVersion = JS_GetVersion(cx); JS_SET_RVAL(cx, vp, INT_TO_JSVAL(origVersion)); if (argc > 0 && JSVAL_IS_INT(JS_ARGV(cx, vp)[0])) JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(JSVAL_TO_INT(JS_ARGV(cx, vp)[0]))); return true; } static JSBool BuildDate(JSContext *cx, unsigned argc, jsval *vp) { fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static JSBool Quit(JSContext *cx, unsigned argc, jsval *vp) { gExitCode = 0; JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp),"/ i", &gExitCode); gQuitting = true; // exit(0); return false; } // Provide script a way to disable the xpcshell error reporter, preventing // reported errors from being logged to the console and also from affecting the // exit code returned by the xpcshell binary. static JSBool IgnoreReportedErrors(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 1 || !args[0].isBoolean()) { JS_ReportError(cx, "Bad arguments"); return false; } gIgnoreReportedErrors = args[0].toBoolean(); return true; } static JSBool DumpXPC(JSContext *cx, unsigned argc, jsval *vp) { int32_t depth = 2; if (argc > 0) { if (!JS_ValueToInt32(cx, JS_ARGV(cx, vp)[0], &depth)) return false; } nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (xpc) xpc->DebugDump(int16_t(depth)); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static JSBool GC(JSContext *cx, unsigned argc, jsval *vp) { JSRuntime *rt = JS_GetRuntime(cx); JS_GC(rt); #ifdef JS_GCMETER js_DumpGCStats(rt, stdout); #endif JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } #ifdef JS_GC_ZEAL static JSBool GCZeal(JSContext *cx, unsigned argc, jsval *vp) { uint32_t zeal; if (!JS_ValueToECMAUint32(cx, argc ? JS_ARGV(cx, vp)[0] : JSVAL_VOID, &zeal)) return false; JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } #endif #ifdef DEBUG static JSBool DumpHeap(JSContext *cx, unsigned argc, jsval *vp) { void* startThing = NULL; JSGCTraceKind startTraceKind = JSTRACE_OBJECT; void *thingToFind = NULL; size_t maxDepth = (size_t)-1; void *thingToIgnore = NULL; FILE *dumpFile; JSBool ok; jsval *argv = JS_ARGV(cx, vp); JS_SET_RVAL(cx, vp, JSVAL_VOID); vp = argv + 0; JSAutoByteString fileName; if (argc > 0 && *vp != JSVAL_NULL && *vp != JSVAL_VOID) { JSString *str; str = JS_ValueToString(cx, *vp); if (!str) return false; *vp = STRING_TO_JSVAL(str); if (!fileName.encodeLatin1(cx, str)) return false; } vp = argv + 1; if (argc > 1 && *vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (!JSVAL_IS_TRACEABLE(*vp)) goto not_traceable_arg; startThing = JSVAL_TO_TRACEABLE(*vp); startTraceKind = JSVAL_TRACE_KIND(*vp); } vp = argv + 2; if (argc > 2 && *vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (!JSVAL_IS_TRACEABLE(*vp)) goto not_traceable_arg; thingToFind = JSVAL_TO_TRACEABLE(*vp); } vp = argv + 3; if (argc > 3 && *vp != JSVAL_NULL && *vp != JSVAL_VOID) { uint32_t depth; if (!JS_ValueToECMAUint32(cx, *vp, &depth)) return false; maxDepth = depth; } vp = argv + 4; if (argc > 4 && *vp != JSVAL_NULL && *vp != JSVAL_VOID) { if (!JSVAL_IS_TRACEABLE(*vp)) goto not_traceable_arg; thingToIgnore = JSVAL_TO_TRACEABLE(*vp); } if (!fileName) { dumpFile = gOutFile; } else { dumpFile = fopen(fileName.ptr(), "w"); if (!dumpFile) { fprintf(gErrFile, "dumpHeap: can't open %s: %s\n", fileName.ptr(), strerror(errno)); return false; } } ok = JS_DumpHeap(JS_GetRuntime(cx), dumpFile, startThing, startTraceKind, thingToFind, maxDepth, thingToIgnore); if (dumpFile != gOutFile) fclose(dumpFile); if (!ok) JS_ReportOutOfMemory(cx); return ok; not_traceable_arg: fprintf(gErrFile, "dumpHeap: argument %u is not null or a heap-allocated thing\n", (unsigned)(vp - argv)); return false; } #endif /* DEBUG */ static JSBool SendCommand(JSContext* cx, unsigned argc, jsval* vp) { if (argc == 0) { JS_ReportError(cx, "Function takes at least one argument!"); return false; } jsval *argv = JS_ARGV(cx, vp); JSString* str = JS_ValueToString(cx, argv[0]); if (!str) { JS_ReportError(cx, "Could not convert argument 1 to string!"); return false; } if (argc > 1 && JS_TypeOfValue(cx, argv[1]) != JSTYPE_FUNCTION) { JS_ReportError(cx, "Could not convert argument 2 to function!"); return false; } if (!XRE_SendTestShellCommand(cx, str, argc > 1 ? &argv[1] : nullptr)) { JS_ReportError(cx, "Couldn't send command!"); return false; } JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } /* * JSContext option name to flag map. The option names are in alphabetical * order for better reporting. */ static const struct JSOption { const char *name; uint32_t flag; } js_options[] = { {"strict", JSOPTION_EXTRA_WARNINGS}, {"werror", JSOPTION_WERROR}, {"strict_mode", JSOPTION_STRICT_MODE}, }; static uint32_t MapContextOptionNameToFlag(JSContext* cx, const char* name) { for (size_t i = 0; i < ArrayLength(js_options); ++i) { if (strcmp(name, js_options[i].name) == 0) return js_options[i].flag; } char* msg = JS_sprintf_append(NULL, "unknown option name '%s'." " The valid names are ", name); for (size_t i = 0; i < ArrayLength(js_options); ++i) { if (!msg) break; msg = JS_sprintf_append(msg, "%s%s", js_options[i].name, (i + 2 < ArrayLength(js_options) ? ", " : i + 2 == ArrayLength(js_options) ? " and " : ".")); } if (!msg) { JS_ReportOutOfMemory(cx); } else { JS_ReportError(cx, msg); free(msg); } return 0; } static JSBool Options(JSContext *cx, unsigned argc, jsval *vp) { uint32_t optset, flag; JSString *str; char *names; JSBool found; optset = 0; jsval *argv = JS_ARGV(cx, vp); for (unsigned i = 0; i < argc; i++) { str = JS_ValueToString(cx, argv[i]); if (!str) return false; argv[i] = STRING_TO_JSVAL(str); JSAutoByteString opt(cx, str); if (!opt) return false; flag = MapContextOptionNameToFlag(cx, opt.ptr()); if (!flag) return false; optset |= flag; } optset = JS_ToggleOptions(cx, optset); names = NULL; found = false; for (size_t i = 0; i < ArrayLength(js_options); i++) { if (js_options[i].flag & optset) { found = true; names = JS_sprintf_append(names, "%s%s", names ? "," : "", js_options[i].name); if (!names) break; } } if (!found) names = strdup(""); if (!names) { JS_ReportOutOfMemory(cx); return false; } str = JS_NewStringCopyZ(cx, names); free(names); if (!str) return false; JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str)); return true; } static JSBool Parent(JSContext *cx, unsigned argc, jsval *vp) { if (argc != 1) { JS_ReportError(cx, "Wrong number of arguments"); return false; } jsval v = JS_ARGV(cx, vp)[0]; if (JSVAL_IS_PRIMITIVE(v)) { JS_ReportError(cx, "Only objects have parents!"); return false; } *vp = OBJECT_TO_JSVAL(JS_GetParent(JSVAL_TO_OBJECT(v))); return true; } static JSBool Atob(JSContext *cx, unsigned argc, jsval *vp) { if (!argc) return true; return xpc::Base64Decode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp)); } static JSBool Btoa(JSContext *cx, unsigned argc, jsval *vp) { if (!argc) return true; return xpc::Base64Encode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp)); } static JSBool Blob(JSContext *cx, unsigned argc, jsval *vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); nsCOMPtr native = do_CreateInstance("@mozilla.org/dom/multipart-blob;1"); if (!native) { JS_ReportError(cx, "Could not create native object!"); return false; } nsCOMPtr initializer = do_QueryInterface(native); NS_ASSERTION(initializer, "what?"); nsresult rv = initializer->Initialize(nullptr, cx, nullptr, args); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not initialize native object!"); return false; } nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, &rv); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not get XPConnent service!"); return false; } JSObject* global = JS_GetGlobalForScopeChain(cx); rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr, &NS_GET_IID(nsISupports), true, args.rval().address(), nullptr); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not wrap native object!"); return false; } return true; } static JSBool File(JSContext *cx, unsigned argc, jsval *vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); nsCOMPtr native = do_CreateInstance("@mozilla.org/dom/multipart-file;1"); if (!native) { JS_ReportError(cx, "Could not create native object!"); return false; } nsCOMPtr initializer = do_QueryInterface(native); NS_ASSERTION(initializer, "what?"); nsresult rv = initializer->Initialize(nullptr, cx, nullptr, args); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not initialize native object!"); return false; } nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, &rv); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not get XPConnent service!"); return false; } JSObject* global = JS_GetGlobalForScopeChain(cx); rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr, &NS_GET_IID(nsISupports), true, args.rval().address(), nullptr); if (NS_FAILED(rv)) { JS_ReportError(cx, "Could not wrap native object!"); return false; } return true; } Value sScriptedOperationCallback = UndefinedValue(); static JSBool XPCShellOperationCallback(JSContext *cx) { // If no operation callback was set by script, no-op. if (sScriptedOperationCallback.isUndefined()) return true; JSAutoCompartment ac(cx, &sScriptedOperationCallback.toObject()); RootedValue rv(cx); if (!JS_CallFunctionValue(cx, nullptr, sScriptedOperationCallback, 0, nullptr, rv.address()) || !rv.isBoolean()) { NS_WARNING("Scripted operation callback failed! Terminating script."); JS_ClearPendingException(cx); return false; } return rv.toBoolean(); } static JSBool SetOperationCallback(JSContext *cx, unsigned argc, jsval *vp) { // Sanity-check args. JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportError(cx, "Wrong number of arguments"); return false; } // Allow callers to remove the operation callback by passing undefined. if (args[0].isUndefined()) { sScriptedOperationCallback = UndefinedValue(); return true; } // Otherwise, we should have a callable object. if (!args[0].isObject() || !JS_ObjectIsCallable(cx, &args[0].toObject())) { JS_ReportError(cx, "Argument must be callable"); return false; } sScriptedOperationCallback = args[0]; return true; } static const JSFunctionSpec glob_functions[] = { JS_FS("print", Print, 0,0), JS_FS("readline", ReadLine, 1,0), JS_FS("load", Load, 1,0), JS_FS("quit", Quit, 0,0), JS_FS("ignoreReportedErrors", IgnoreReportedErrors, 1,0), JS_FS("version", Version, 1,0), JS_FS("build", BuildDate, 0,0), JS_FS("dumpXPC", DumpXPC, 1,0), JS_FS("dump", Dump, 1,0), JS_FS("gc", GC, 0,0), #ifdef JS_GC_ZEAL JS_FS("gczeal", GCZeal, 1,0), #endif JS_FS("options", Options, 0,0), JS_FN("parent", Parent, 1,0), #ifdef DEBUG JS_FS("dumpHeap", DumpHeap, 5,0), #endif JS_FS("sendCommand", SendCommand, 1,0), JS_FS("atob", Atob, 1,0), JS_FS("btoa", Btoa, 1,0), JS_FS("Blob", Blob, 2,JSFUN_CONSTRUCTOR), JS_FS("File", File, 2,JSFUN_CONSTRUCTOR), JS_FS("setOperationCallback", SetOperationCallback, 1,0), JS_FS_END }; JSClass global_class = { "global", 0, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr }; static JSBool env_setProperty(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, MutableHandleValue vp) { /* XXX porting may be easy, but these don't seem to supply setenv by default */ #if !defined XP_OS2 && !defined SOLARIS JSString *valstr; JS::Rooted idstr(cx); int rv; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return false; idstr = JS_ValueToString(cx, idval); valstr = JS_ValueToString(cx, vp); if (!idstr || !valstr) return false; JSAutoByteString name(cx, idstr); if (!name) return false; JSAutoByteString value(cx, valstr); if (!value) return false; #if defined XP_WIN || defined HPUX || defined OSF1 || defined SCO { char *waste = JS_smprintf("%s=%s", name.ptr(), value.ptr()); if (!waste) { JS_ReportOutOfMemory(cx); return false; } rv = putenv(waste); #ifdef XP_WIN /* * HPUX9 at least still has the bad old non-copying putenv. * * Per mail from , OSF1 also has a putenv * that will crash if you pass it an auto char array (so it must place * its argument directly in the char *environ[] array). */ free(waste); #endif } #else rv = setenv(name.ptr(), value.ptr(), 1); #endif if (rv < 0) { JS_ReportError(cx, "can't set envariable %s to %s", name.ptr(), value.ptr()); return false; } vp.set(STRING_TO_JSVAL(valstr)); #endif /* !defined XP_OS2 && !defined SOLARIS */ return true; } static JSBool env_enumerate(JSContext *cx, HandleObject obj) { static JSBool reflected; char **evp, *name, *value; JSString *valstr; JSBool ok; if (reflected) return true; for (evp = (char **)JS_GetPrivate(obj); (name = *evp) != NULL; evp++) { value = strchr(name, '='); if (!value) continue; *value++ = '\0'; valstr = JS_NewStringCopyZ(cx, value); if (!valstr) { ok = false; } else { ok = JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), NULL, NULL, JSPROP_ENUMERATE); } value[-1] = '='; if (!ok) return false; } reflected = true; return true; } static JSBool env_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags, JS::MutableHandleObject objp) { JSString *idstr, *valstr; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return false; idstr = JS_ValueToString(cx, idval); if (!idstr) return false; JSAutoByteString name(cx, idstr); if (!name) return false; const char *value = getenv(name.ptr()); if (value) { valstr = JS_NewStringCopyZ(cx, value); if (!valstr) return false; if (!JS_DefinePropertyById(cx, obj, id, STRING_TO_JSVAL(valstr), NULL, NULL, JSPROP_ENUMERATE)) { return false; } objp.set(obj); } return true; } static JSClass env_class = { "environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, env_setProperty, env_enumerate, (JSResolveOp) env_resolve, JS_ConvertStub, nullptr }; /***************************************************************************/ typedef enum JSShellErrNum { #define MSG_DEF(name, number, count, exception, format) \ name = number, #include "jsshell.msg" #undef MSG_DEF JSShellErr_Limit } JSShellErrNum; JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { #define MSG_DEF(name, number, count, exception, format) \ { format, count } , #include "jsshell.msg" #undef MSG_DEF }; static const JSErrorFormatString * my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber) { if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) return NULL; return &jsShell_ErrorFormatString[errorNumber]; } static void ProcessFile(JSContext *cx, JS::Handle obj, const char *filename, FILE *file, JSBool forceTTY) { JSScript *script; JS::Rooted result(cx); int lineno, startline; JSBool ok, hitEOF; char *bufp, buffer[4096]; JSString *str; if (forceTTY) { file = stdin; } else #ifdef HAVE_ISATTY if (!isatty(fileno(file))) #endif { /* * It's not interactive - just execute it. * * Support the UNIX #! shell hack; gobble the first line if it starts * with '#'. TODO - this isn't quite compatible with sharp variables, * as a legal js program (using sharp variables) might start with '#'. * But that would require multi-character lookahead. */ int ch = fgetc(file); if (ch == '#') { while ((ch = fgetc(file)) != EOF) { if (ch == '\n' || ch == '\r') break; } } ungetc(ch, file); DoBeginRequest(cx); JS::CompileOptions options(cx); options.setUTF8(true) .setFileAndLine(filename, 1) .setPrincipals(gJSPrincipals); script = JS::Compile(cx, obj, options, file); if (script && !compileOnly) (void)JS_ExecuteScript(cx, obj, script, result.address()); DoEndRequest(cx); return; } /* It's an interactive filehandle; drop into read-eval-print loop. */ lineno = 1; hitEOF = false; do { bufp = buffer; *bufp = '\0'; /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles * cleanly. This should be whenever we get a complete statement that * coincides with the end of a line. */ startline = lineno; do { if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { hitEOF = true; break; } bufp += strlen(bufp); lineno++; } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer))); DoBeginRequest(cx); /* Clear any pending exception from previous failed compiles. */ JS_ClearPendingException(cx); script = JS_CompileScriptForPrincipals(cx, obj, gJSPrincipals, buffer, strlen(buffer), "typein", startline); if (script) { JSErrorReporter older; if (!compileOnly) { ok = JS_ExecuteScript(cx, obj, script, result.address()); if (ok && result != JSVAL_VOID) { /* Suppress error reports from JS_ValueToString(). */ older = JS_SetErrorReporter(cx, NULL); str = JS_ValueToString(cx, result); JS_SetErrorReporter(cx, older); JSAutoByteString bytes; if (str && bytes.encodeLatin1(cx, str)) fprintf(gOutFile, "%s\n", bytes.ptr()); else ok = false; } } } DoEndRequest(cx); } while (!hitEOF && !gQuitting); fprintf(gOutFile, "\n"); } static void Process(JSContext *cx, JS::Handle obj, const char *filename, JSBool forceTTY) { FILE *file; if (forceTTY || !filename || strcmp(filename, "-") == 0) { file = stdin; } else { file = fopen(filename, "r"); if (!file) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_CANT_OPEN, filename, strerror(errno)); gExitCode = EXITCODE_FILE_NOT_FOUND; return; } } ProcessFile(cx, obj, filename, file, forceTTY); if (file != stdin) fclose(file); } static int usage(void) { fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); fprintf(gErrFile, "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-PsSwWCijmIn] [-v version] [-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n"); return 2; } extern JSClass global_class; static void ProcessArgsForCompartment(JSContext *cx, char **argv, int argc) { for (int i = 0; i < argc; i++) { if (argv[i][0] != '-' || argv[i][1] == '\0') break; switch (argv[i][1]) { case 'v': case 'f': case 'e': if (++i == argc) return; break; case 'S': JS_ToggleOptions(cx, JSOPTION_WERROR); case 's': JS_ToggleOptions(cx, JSOPTION_EXTRA_WARNINGS); break; case 'I': JS_ToggleOptions(cx, JSOPTION_COMPILE_N_GO); JS_ToggleOptions(cx, JSOPTION_ION); JS_ToggleOptions(cx, JSOPTION_ASMJS); break; case 'n': JS_ToggleOptions(cx, JSOPTION_TYPE_INFERENCE); break; } } } static int ProcessArgs(JSContext *cx, JS::Handle obj, char **argv, int argc, XPCShellDirProvider* aDirProvider) { const char rcfilename[] = "xpcshell.js"; FILE *rcfile; int i; JS::Rooted argsObj(cx); char *filename = NULL; JSBool isInteractive = true; JSBool forceTTY = false; rcfile = fopen(rcfilename, "r"); if (rcfile) { printf("[loading '%s'...]\n", rcfilename); ProcessFile(cx, obj, rcfilename, rcfile, false); fclose(rcfile); } /* * Scan past all optional arguments so we can create the arguments object * before processing any -f options, which must interleave properly with * -v and -w options. This requires two passes, and without getopt, we'll * have to keep the option logic here and in the second for loop in sync. */ for (i = 0; i < argc; i++) { if (argv[i][0] != '-' || argv[i][1] == '\0') { ++i; break; } switch (argv[i][1]) { case 'v': case 'f': case 'e': ++i; break; default:; } } /* * Create arguments early and define it to root it, so it's safe from any * GC calls nested below, and so it is available to -f arguments. */ argsObj = JS_NewArrayObject(cx, 0, NULL); if (!argsObj) return 1; if (!JS_DefineProperty(cx, obj, "arguments", OBJECT_TO_JSVAL(argsObj), NULL, NULL, 0)) { return 1; } for (size_t j = 0, length = argc - i; j < length; j++) { JSString *str = JS_NewStringCopyZ(cx, argv[i++]); if (!str) return 1; if (!JS_DefineElement(cx, argsObj, j, STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE)) { return 1; } } for (i = 0; i < argc; i++) { if (argv[i][0] != '-' || argv[i][1] == '\0') { filename = argv[i++]; isInteractive = false; break; } switch (argv[i][1]) { case 'v': if (++i == argc) { return usage(); } JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(atoi(argv[i]))); break; case 'W': reportWarnings = false; break; case 'w': reportWarnings = true; break; case 'x': break; case 'd': xpc_ActivateDebugMode(); break; case 'f': if (++i == argc) { return usage(); } Process(cx, obj, argv[i], false); /* * XXX: js -f foo.js should interpret foo.js and then * drop into interactive mode, but that breaks test * harness. Just execute foo.js for now. */ isInteractive = false; break; case 'i': isInteractive = forceTTY = true; break; case 'e': { jsval rval; if (++i == argc) { return usage(); } JS_EvaluateScriptForPrincipals(cx, obj, gJSPrincipals, argv[i], strlen(argv[i]), "-e", 1, &rval); isInteractive = false; break; } case 'C': compileOnly = true; isInteractive = false; break; case 'S': case 's': case 'm': case 'I': case 'n': // These options are processed in ProcessArgsForCompartment. break; case 'p': { // plugins path char *pluginPath = argv[++i]; nsCOMPtr pluginsDir; if (NS_FAILED(XRE_GetFileFromPath(pluginPath, getter_AddRefs(pluginsDir)))) { fprintf(gErrFile, "Couldn't use given plugins dir.\n"); return usage(); } aDirProvider->SetPluginDir(pluginsDir); break; } default: return usage(); } } if (filename || isInteractive) Process(cx, obj, filename, forceTTY); return gExitCode; } /***************************************************************************/ // #define TEST_InitClassesWithNewWrappedGlobal #ifdef TEST_InitClassesWithNewWrappedGlobal // XXX hacky test code... #include "xpctest.h" class TestGlobal : public nsIXPCTestNoisy, public nsIXPCScriptable { public: NS_DECL_ISUPPORTS NS_DECL_NSIXPCTESTNOISY NS_DECL_NSIXPCSCRIPTABLE TestGlobal(){} }; NS_IMPL_ISUPPORTS2(TestGlobal, nsIXPCTestNoisy, nsIXPCScriptable) // The nsIXPCScriptable map declaration that will generate stubs for us... #define XPC_MAP_CLASSNAME TestGlobal #define XPC_MAP_QUOTED_CLASSNAME "TestGlobal" #define XPC_MAP_FLAGS nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY |\ nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY |\ nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY #include "xpc_map_end.h" /* This will #undef the above */ NS_IMETHODIMP TestGlobal::Squawk() {return NS_OK;} #endif // uncomment to install the test 'this' translator // #define TEST_TranslateThis #ifdef TEST_TranslateThis #include "xpctest.h" class nsXPCFunctionThisTranslator : public nsIXPCFunctionThisTranslator { public: NS_DECL_ISUPPORTS NS_DECL_NSIXPCFUNCTIONTHISTRANSLATOR nsXPCFunctionThisTranslator(); virtual ~nsXPCFunctionThisTranslator(); /* additional members */ }; /* Implementation file */ NS_IMPL_ISUPPORTS1(nsXPCFunctionThisTranslator, nsIXPCFunctionThisTranslator) nsXPCFunctionThisTranslator::nsXPCFunctionThisTranslator() { /* member initializers and constructor code */ } nsXPCFunctionThisTranslator::~nsXPCFunctionThisTranslator() { /* destructor code */ #ifdef DEBUG_jband printf("destroying nsXPCFunctionThisTranslator\n"); #endif } /* nsISupports TranslateThis (in nsISupports aInitialThis); */ NS_IMETHODIMP nsXPCFunctionThisTranslator::TranslateThis(nsISupports *aInitialThis, nsISupports **_retval) { NS_IF_ADDREF(aInitialThis); *_retval = aInitialThis; return NS_OK; } #endif // ContextCallback calls are chained static JSContextCallback gOldJSContextCallback; void XPCShellErrorReporter(JSContext *cx, const char *message, JSErrorReport *rep) { if (gIgnoreReportedErrors) return; if (!JSREPORT_IS_WARNING(rep->flags)) gExitCode = EXITCODE_RUNTIME_ERROR; // Delegate to the system error reporter for heavy lifting. xpc::SystemErrorReporterExternal(cx, message, rep); } static JSBool ContextCallback(JSContext *cx, unsigned contextOp) { if (gOldJSContextCallback && !gOldJSContextCallback(cx, contextOp)) return false; if (contextOp == JSCONTEXT_NEW) { JS_SetErrorReporter(cx, XPCShellErrorReporter); JS_SetOperationCallback(cx, XPCShellOperationCallback); } return true; } static bool GetCurrentWorkingDirectory(nsAString& workingDirectory) { #if !defined(XP_WIN) && !defined(XP_UNIX) //XXX: your platform should really implement this return false; #elif XP_WIN DWORD requiredLength = GetCurrentDirectoryW(0, NULL); workingDirectory.SetLength(requiredLength); GetCurrentDirectoryW(workingDirectory.Length(), (LPWSTR)workingDirectory.BeginWriting()); // we got a trailing null there workingDirectory.SetLength(requiredLength); workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\'); #elif defined(XP_UNIX) nsAutoCString cwd; // 1024 is just a guess at a sane starting value size_t bufsize = 1024; char* result = nullptr; while (result == nullptr) { if (!cwd.SetLength(bufsize)) return false; result = getcwd(cwd.BeginWriting(), cwd.Length()); if (!result) { if (errno != ERANGE) return false; // need to make the buffer bigger bufsize *= 2; } } // size back down to the actual string length cwd.SetLength(strlen(result) + 1); cwd.Replace(cwd.Length() - 1, 1, '/'); workingDirectory = NS_ConvertUTF8toUTF16(cwd); #endif return true; } static JSSecurityCallbacks shellSecurityCallbacks; int main(int argc, char **argv, char **envp) { #ifdef XP_MACOSX InitAutoreleasePool(); #endif JSRuntime *rt; JSContext *cx; int result; nsresult rv; #ifdef HAVE_SETBUF // unbuffer stdout so that output is in the correct order; note that stderr // is unbuffered by default setbuf(stdout, 0); #endif #ifdef XRE_HAS_DLL_BLOCKLIST XRE_SetupDllBlocklist(); #endif gErrFile = stderr; gOutFile = stdout; gInFile = stdin; NS_LogInit(); nsCOMPtr appFile; rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(appFile)); if (NS_FAILED(rv)) { printf("Couldn't find application file.\n"); return 1; } nsCOMPtr appDir; rv = appFile->GetParent(getter_AddRefs(appDir)); if (NS_FAILED(rv)) { printf("Couldn't get application directory.\n"); return 1; } XPCShellDirProvider dirprovider; dirprovider.SetAppFile(appFile); if (argc > 1 && !strcmp(argv[1], "-g")) { if (argc < 3) return usage(); if (!dirprovider.SetGREDir(argv[2])) { printf("SetGREDir failed.\n"); return 1; } argc -= 2; argv += 2; } if (argc > 1 && !strcmp(argv[1], "-a")) { if (argc < 3) return usage(); nsCOMPtr dir; rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir)); if (NS_SUCCEEDED(rv)) { appDir = do_QueryInterface(dir, &rv); dirprovider.SetAppDir(appDir); } if (NS_FAILED(rv)) { printf("Couldn't use given appdir.\n"); return 1; } argc -= 2; argv += 2; } while (argc > 1 && !strcmp(argv[1], "-r")) { if (argc < 3) return usage(); nsCOMPtr lf; rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf)); if (NS_FAILED(rv)) { printf("Couldn't get manifest file.\n"); return 1; } XRE_AddManifestLocation(NS_COMPONENT_LOCATION, lf); argc -= 2; argv += 2; } { if (argc > 1 && !strcmp(argv[1], "--greomni")) { nsCOMPtr greOmni; nsCOMPtr appOmni; XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni)); if (argc > 3 && !strcmp(argv[3], "--appomni")) { XRE_GetFileFromPath(argv[4], getter_AddRefs(appOmni)); argc-=2; argv+=2; } else { appOmni = greOmni; } XRE_InitOmnijar(greOmni, appOmni); argc-=2; argv+=2; } nsCOMPtr servMan; rv = NS_InitXPCOM2(getter_AddRefs(servMan), appDir, &dirprovider); if (NS_FAILED(rv)) { printf("NS_InitXPCOM2 failed!\n"); return 1; } nsCOMPtr rtsvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); // get the JSRuntime from the runtime svc if (!rtsvc) { printf("failed to get nsJSRuntimeService!\n"); return 1; } if (NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) { printf("failed to get JSRuntime from nsJSRuntimeService!\n"); return 1; } gOldJSContextCallback = JS_SetContextCallback(rt, ContextCallback); cx = JS_NewContext(rt, 8192); if (!cx) { printf("JS_NewContext failed!\n"); return 1; } argc--; argv++; ProcessArgsForCompartment(cx, argv, argc); nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (!xpc) { printf("failed to get nsXPConnect service!\n"); return 1; } nsCOMPtr systemprincipal; // Fetch the system principal and store it away in a global, to use for // script compilation in Load() and ProcessFile() (including interactive // eval loop) { nsCOMPtr securityManager = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); if (NS_SUCCEEDED(rv) && securityManager) { rv = securityManager->GetSystemPrincipal(getter_AddRefs(systemprincipal)); if (NS_FAILED(rv)) { fprintf(gErrFile, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n"); } else { // fetch the JS principals and stick in a global gJSPrincipals = nsJSPrincipals::get(systemprincipal); JS_HoldPrincipals(gJSPrincipals); } } else { fprintf(gErrFile, "+++ Failed to get ScriptSecurityManager service, running without principals"); } } const JSSecurityCallbacks *scb = JS_GetSecurityCallbacks(rt); NS_ASSERTION(scb, "We are assuming that nsScriptSecurityManager::Init() has been run"); shellSecurityCallbacks = *scb; JS_SetSecurityCallbacks(rt, &shellSecurityCallbacks); #ifdef TEST_TranslateThis nsCOMPtr translator(new nsXPCFunctionThisTranslator); xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator); #endif nsCxPusher pusher; pusher.Push(cx); nsRefPtr backstagePass; rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); if (NS_FAILED(rv)) { fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n", static_cast(rv)); return 1; } JS::CompartmentOptions options; options.setZone(JS::SystemZone) .setVersion(JSVERSION_LATEST); nsCOMPtr holder; rv = xpc->InitClassesWithNewWrappedGlobal(cx, static_cast(backstagePass), systemprincipal, 0, options, getter_AddRefs(holder)); if (NS_FAILED(rv)) return 1; { JS::Rooted glob(cx, holder->GetJSObject()); if (!glob) { return 1; } backstagePass->SetGlobalObject(glob); JSAutoCompartment ac(cx, glob); if (!JS_InitReflect(cx, glob)) { JS_EndRequest(cx); return 1; } if (!JS_DefineFunctions(cx, glob, glob_functions) || !JS_DefineProfilingFunctions(cx, glob)) { JS_EndRequest(cx); return 1; } JS::Rooted envobj(cx); envobj = JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0); if (!envobj) { JS_EndRequest(cx); return 1; } JS_SetPrivate(envobj, envp); nsAutoString workingDirectory; if (GetCurrentWorkingDirectory(workingDirectory)) gWorkingDirectory = &workingDirectory; JS_DefineProperty(cx, glob, "__LOCATION__", JSVAL_VOID, GetLocationProperty, NULL, 0); JS_AddValueRoot(cx, &sScriptedOperationCallback); result = ProcessArgs(cx, glob, argv, argc, &dirprovider); JS_RemoveValueRoot(cx, &sScriptedOperationCallback); JS_DropPrincipals(rt, gJSPrincipals); JS_SetAllNonReservedSlotsToUndefined(cx, glob); JS_GC(rt); } pusher.Pop(); JS_GC(rt); JS_DestroyContext(cx); } // this scopes the nsCOMPtrs if (!XRE_ShutdownTestShell()) NS_ERROR("problem shutting down testshell"); #ifdef MOZ_CRASHREPORTER // Get the crashreporter service while XPCOM is still active. // This is a special exception: it will remain usable after NS_ShutdownXPCOM(). nsCOMPtr crashReporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1"); #endif // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM rv = NS_ShutdownXPCOM( NULL ); NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); #ifdef TEST_CALL_ON_WRAPPED_JS_AFTER_SHUTDOWN // test of late call and release (see above) JSContext* bogusCX; bogus->Peek(&bogusCX); bogus = nullptr; #endif appDir = nullptr; appFile = nullptr; dirprovider.ClearGREDir(); dirprovider.ClearAppDir(); dirprovider.ClearPluginDir(); dirprovider.ClearAppFile(); #ifdef MOZ_CRASHREPORTER // Shut down the crashreporter service to prevent leaking some strings it holds. if (crashReporter) { crashReporter->SetEnabled(false); crashReporter = nullptr; } #endif NS_LogTerm(); #ifdef XP_MACOSX FinishAutoreleasePool(); #endif return result; } bool XPCShellDirProvider::SetGREDir(const char *dir) { nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir)); return NS_SUCCEEDED(rv); } void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; } void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; } void XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) { mPluginDir = pluginDir; } NS_IMETHODIMP_(nsrefcnt) XPCShellDirProvider::AddRef() { return 2; } NS_IMETHODIMP_(nsrefcnt) XPCShellDirProvider::Release() { return 1; } NS_IMPL_QUERY_INTERFACE2(XPCShellDirProvider, nsIDirectoryServiceProvider, nsIDirectoryServiceProvider2) NS_IMETHODIMP XPCShellDirProvider::GetFile(const char *prop, bool *persistent, nsIFile* *result) { if (mGREDir && !strcmp(prop, NS_GRE_DIR)) { *persistent = true; return mGREDir->Clone(result); } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) { *persistent = true; return mAppFile->Clone(result); } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) { nsCOMPtr file; *persistent = true; if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) || NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("defaults"))) || NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("pref")))) return NS_ERROR_FAILURE; NS_ADDREF(*result = file); return NS_OK; } else if (mAppFile && !strcmp(prop, XRE_UPDATE_ROOT_DIR)) { // For xpcshell, we pretend that the update root directory is always // the same as the GRE directory, except for Windows, where we immitate // the algorithm defined in nsXREDirProvider::GetUpdateRootDir. *persistent = true; #ifdef XP_WIN char appData[MAX_PATH] = {'\0'}; char path[MAX_PATH] = {'\0'}; LPITEMIDLIST pItemIDList; if (FAILED(SHGetSpecialFolderLocation(NULL, CSIDL_LOCAL_APPDATA, &pItemIDList)) || FAILED(SHGetPathFromIDListA(pItemIDList, appData))) { return NS_ERROR_FAILURE; } nsAutoString pathName; pathName.AssignASCII(appData); nsCOMPtr localFile; nsresult rv = NS_NewLocalFile(pathName, true, getter_AddRefs(localFile)); if (NS_FAILED(rv)) { return rv; } #ifdef MOZ_APP_PROFILE localFile->AppendNative(NS_LITERAL_CSTRING(MOZ_APP_PROFILE)); #else // MOZ_APP_VENDOR and MOZ_APP_BASENAME are optional. #ifdef MOZ_APP_VENDOR localFile->AppendNative(NS_LITERAL_CSTRING(MOZ_APP_VENDOR)); #endif #ifdef MOZ_APP_BASENAME localFile->AppendNative(NS_LITERAL_CSTRING(MOZ_APP_BASENAME)); #endif // However app name is always appended. localFile->AppendNative(NS_LITERAL_CSTRING(MOZ_APP_NAME)); #endif return localFile->Clone(result); #else return mAppFile->GetParent(result); #endif } return NS_ERROR_FAILURE; } NS_IMETHODIMP XPCShellDirProvider::GetFiles(const char *prop, nsISimpleEnumerator* *result) { if (mGREDir && !strcmp(prop, "ChromeML")) { nsCOMArray dirs; nsCOMPtr file; mGREDir->Clone(getter_AddRefs(file)); file->AppendNative(NS_LITERAL_CSTRING("chrome")); dirs.AppendObject(file); nsresult rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) dirs.AppendObject(file); return NS_NewArrayEnumerator(result, dirs); } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { nsCOMArray dirs; nsCOMPtr appDir; bool exists; if (mAppDir && NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) && NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("defaults"))) && NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("preferences"))) && NS_SUCCEEDED(appDir->Exists(&exists)) && exists) { dirs.AppendObject(appDir); return NS_NewArrayEnumerator(result, dirs); } return NS_ERROR_FAILURE; } else if (!strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) { nsCOMPtr file; nsCOMArray dirs; bool exists; // We have to add this path, buildbot copies the test plugin directory // to (app)/bin when unpacking test zips. if (mGREDir) { mGREDir->Clone(getter_AddRefs(file)); if (NS_SUCCEEDED(mGREDir->Clone(getter_AddRefs(file)))) { file->AppendNative(NS_LITERAL_CSTRING("plugins")); if (NS_SUCCEEDED(file->Exists(&exists)) && exists) { dirs.AppendObject(file); } } } // Add the test plugin location passed in by the caller or through // runxpcshelltests. if (mPluginDir) { dirs.AppendObject(mPluginDir); } return NS_NewArrayEnumerator(result, dirs); } return NS_ERROR_FAILURE; }