/* -*- 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 "nsXULAppAPI.h" #include "jsapi.h" #include "jsfriendapi.h" #include "jsprf.h" #include "mozilla/ChaosMode.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" #include "nsServiceManagerUtils.h" #include "nsComponentManagerUtils.h" #include "nsIXPConnect.h" #include "nsIServiceManager.h" #include "nsIFile.h" #include "nsString.h" #include "nsIDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nscore.h" #include "nsArrayEnumerator.h" #include "nsCOMArray.h" #include "nsDirectoryServiceUtils.h" #include "nsCOMPtr.h" #include "nsJSPrincipals.h" #include "xpcpublic.h" #include "xpcprivate.h" #include "BackstagePass.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsJSUtils.h" #include "gfxPrefs.h" #include "nsIXULRuntime.h" #include "base/histogram.h" #ifdef ANDROID #include #endif #ifdef XP_WIN #include "mozilla/widget/AudioSession.h" #include #endif // 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 using namespace mozilla; using namespace JS; using mozilla::dom::AutoJSAPI; using mozilla::dom::AutoEntryScript; class XPCShellDirProvider : public nsIDirectoryServiceProvider2 { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIDIRECTORYSERVICEPROVIDER NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 XPCShellDirProvider() { } ~XPCShellDirProvider() { } // The platform resource folder void SetGREDirs(nsIFile* greDir); void ClearGREDirs() { mGREDir = nullptr; mGREBinDir = 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 mGREBinDir; nsCOMPtr mAppDir; nsCOMPtr mPluginDir; nsCOMPtr mAppFile; }; #ifdef XP_WIN class MOZ_STACK_CLASS AutoAudioSession { public: AutoAudioSession() { widget::StartAudioSession(); } ~AutoAudioSession() { widget::StopAudioSession(); } }; #endif static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; #define EXITCODE_RUNTIME_ERROR 3 #define EXITCODE_FILE_NOT_FOUND 4 static FILE* gOutFile = nullptr; static FILE* gErrFile = nullptr; static FILE* gInFile = nullptr; static int gExitCode = 0; static bool gQuitting = false; static bool reportWarnings = true; static bool compileOnly = false; static JSPrincipals* gJSPrincipals = nullptr; static nsAutoString* gWorkingDirectory = nullptr; static bool GetLocationProperty(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { JS_ReportErrorASCII(cx, "Unexpected this value for GetLocationProperty"); return false; } #if !defined(XP_WIN) && !defined(XP_UNIX) //XXX: your platform should really implement this return false; #else JS::AutoFilename filename; if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) { 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.get(), -1, nullptr, 0); nsAutoString filenameString; filenameString.SetLength(bufferSize); MultiByteToWideChar(CP_ACP, 0, filename.get(), -1, (LPWSTR)filenameString.BeginWriting(), filenameString.Length()); // remove the null terminator filenameString.SetLength(bufferSize - 1); // replace forward slashes with backslashes, // since nsLocalFileWin chokes on them char16_t* start = filenameString.BeginWriting(); char16_t* end = filenameString.EndWriting(); while (start != end) { if (*start == L'/') *start = L'\\'; start++; } #elif defined(XP_UNIX) NS_ConvertUTF8toUTF16 filenameString(filename.get()); #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) { bool symlink; // don't normalize symlinks, because that's kind of confusing if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && !symlink) location->Normalize(); RootedObject locationObj(cx); rv = xpc->WrapNative(cx, &args.thisv().toObject(), location, NS_GET_IID(nsIFile), locationObj.address()); if (NS_SUCCEEDED(rv) && locationObj) { args.rval().setObject(*locationObj); } } } return true; #endif } static bool GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) { fputs(prompt, gOutFile); fflush(gOutFile); char line[4096] = { '\0' }; while (true) { if (fgets(line, sizeof line, file)) { strcpy(bufp, line); return true; } if (errno != EINTR) { return false; } } } static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, 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]; RootedString str(cx); /* If a prompt was specified, construct the string */ if (args.length() > 0) { str = JS::ToString(cx, args[0]); if (!str) return false; } else { str = JS_GetEmptyString(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)) { args.rval().setNull(); return true; } } else if (buf[buflen - 1] == '\n') { --buflen; } /* Turn buf into a JSString */ str = JS_NewStringCopyN(cx, buf, buflen); if (!str) return false; args.rval().setString(str); return true; } static bool Print(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setUndefined(); RootedString str(cx); nsAutoCString utf8output; for (unsigned i = 0; i < args.length(); i++) { str = ToString(cx, args[i]); if (!str) return false; JSAutoByteString utf8str; if (!utf8str.encodeUtf8(cx, str)) return false; if (i) utf8output.Append(' '); utf8output.Append(utf8str.ptr(), utf8str.length()); } utf8output.Append('\n'); fputs(utf8output.get(), gOutFile); fflush(gOutFile); return true; } static bool Dump(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setUndefined(); if (!args.length()) return true; RootedString str(cx, 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(), gOutFile); fflush(gOutFile); return true; } static bool Load(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JS::Rooted obj(cx, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; if (!JS_IsGlobalObject(obj)) { JS_ReportErrorASCII(cx, "Trying to load() into a non-global object"); return false; } RootedString str(cx); for (unsigned i = 0; i < args.length(); i++) { str = ToString(cx, args[i]); if (!str) return false; JSAutoByteString filename(cx, str); if (!filename) return false; FILE* file = fopen(filename.ptr(), "r"); if (!file) { filename.clear(); if (!filename.encodeUtf8(cx, str)) return false; JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading", filename.ptr()); return false; } JS::CompileOptions options(cx); options.setUTF8(true) .setFileAndLine(filename.ptr(), 1) .setIsRunOnce(true); JS::Rooted script(cx); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); JS::Compile(cx, options, file, &script); fclose(file); if (!script) return false; if (!compileOnly) { if (!JS_ExecuteScript(cx, script)) { return false; } } } args.rval().setUndefined(); return true; } static bool Version(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setInt32(JS_GetVersion(cx)); if (args.get(0).isInt32()) JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(args[0].toInt32())); return true; } static bool Quit(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); gExitCode = 0; if (!ToInt32(cx, args.get(0), &gExitCode)) return false; gQuitting = true; // exit(0); return false; } static bool DumpXPC(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); uint16_t depth = 2; if (args.length() > 0) { if (!JS::ToUint16(cx, args[0], &depth)) return false; } nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (xpc) xpc->DebugDump(int16_t(depth)); args.rval().setUndefined(); return true; } static bool GC(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JS_GC(cx); args.rval().setUndefined(); return true; } static bool SendCommand(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) { JS_ReportErrorASCII(cx, "Function takes at least one argument!"); return false; } RootedString str(cx, ToString(cx, args[0])); if (!str) { JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!"); return false; } if (args.length() > 1 && JS_TypeOfValue(cx, args[1]) != JSTYPE_FUNCTION) { JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!"); return false; } if (!XRE_SendTestShellCommand(cx, str, args.length() > 1 ? args[1].address() : nullptr)) { JS_ReportErrorASCII(cx, "Couldn't send command!"); return false; } args.rval().setUndefined(); return true; } static bool Options(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); ContextOptions oldContextOptions = ContextOptionsRef(cx); RootedString str(cx); JSAutoByteString opt; for (unsigned i = 0; i < args.length(); ++i) { str = ToString(cx, args[i]); if (!str) return false; opt.clear(); if (!opt.encodeUtf8(cx, str)) return false; if (strcmp(opt.ptr(), "strict") == 0) ContextOptionsRef(cx).toggleExtraWarnings(); else if (strcmp(opt.ptr(), "werror") == 0) ContextOptionsRef(cx).toggleWerror(); else if (strcmp(opt.ptr(), "strict_mode") == 0) ContextOptionsRef(cx).toggleStrictMode(); else { JS_ReportErrorUTF8(cx, "unknown option name '%s'. The valid names are " "strict, werror, and strict_mode.", opt.ptr()); return false; } } char* names = nullptr; if (oldContextOptions.extraWarnings()) { names = JS_sprintf_append(names, "%s", "strict"); if (!names) { JS_ReportOutOfMemory(cx); return false; } } if (oldContextOptions.werror()) { names = JS_sprintf_append(names, "%s%s", names ? "," : "", "werror"); if (!names) { JS_ReportOutOfMemory(cx); return false; } } if (names && oldContextOptions.strictMode()) { names = JS_sprintf_append(names, "%s%s", names ? "," : "", "strict_mode"); if (!names) { JS_ReportOutOfMemory(cx); return false; } } str = JS_NewStringCopyZ(cx, names); free(names); if (!str) return false; args.rval().setString(str); return true; } static PersistentRootedValue *sScriptedInterruptCallback = nullptr; static bool XPCShellInterruptCallback(JSContext* cx) { MOZ_ASSERT(sScriptedInterruptCallback->initialized()); RootedValue callback(cx, *sScriptedInterruptCallback); // If no interrupt callback was set by script, no-op. if (callback.isUndefined()) return true; JSAutoCompartment ac(cx, &callback.toObject()); RootedValue rv(cx); if (!JS_CallFunctionValue(cx, nullptr, callback, JS::HandleValueArray::empty(), &rv) || !rv.isBoolean()) { NS_WARNING("Scripted interrupt callback failed! Terminating script."); JS_ClearPendingException(cx); return false; } return rv.toBoolean(); } static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) { MOZ_ASSERT(sScriptedInterruptCallback->initialized()); // Sanity-check args. JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportErrorASCII(cx, "Wrong number of arguments"); return false; } // Allow callers to remove the interrupt callback by passing undefined. if (args[0].isUndefined()) { *sScriptedInterruptCallback = UndefinedValue(); return true; } // Otherwise, we should have a callable object. if (!args[0].isObject() || !JS::IsCallable(&args[0].toObject())) { JS_ReportErrorASCII(cx, "Argument must be callable"); return false; } *sScriptedInterruptCallback = args[0]; return true; } static bool SimulateActivityCallback(JSContext* cx, unsigned argc, Value* vp) { // Sanity-check args. JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() != 1 || !args[0].isBoolean()) { JS_ReportErrorASCII(cx, "Wrong number of arguments"); return false; } xpc::SimulateActivityCallback(args[0].toBoolean()); return true; } static bool RegisterAppManifest(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportErrorASCII(cx, "Wrong number of arguments"); return false; } if (!args[0].isObject()) { JS_ReportErrorASCII(cx, "Expected object as argument 1 to registerAppManifest"); return false; } Rooted arg1(cx, &args[0].toObject()); nsCOMPtr file; nsresult rv = nsXPConnect::XPConnect()-> WrapJS(cx, arg1, NS_GET_IID(nsIFile), getter_AddRefs(file)); if (NS_FAILED(rv)) { XPCThrower::Throw(rv, cx); return false; } rv = XRE_AddManifestLocation(NS_APP_LOCATION, file); if (NS_FAILED(rv)) { XPCThrower::Throw(rv, cx); return false; } 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("version", Version, 1,0), JS_FS("dumpXPC", DumpXPC, 1,0), JS_FS("dump", Dump, 1,0), JS_FS("gc", GC, 0,0), JS_FS("options", Options, 0,0), JS_FS("sendCommand", SendCommand, 1,0), JS_FS("atob", xpc::Atob, 1,0), JS_FS("btoa", xpc::Btoa, 1,0), JS_FS("setInterruptCallback", SetInterruptCallback, 1,0), JS_FS("simulateActivityCallback", SimulateActivityCallback, 1,0), JS_FS("registerAppManifest", RegisterAppManifest, 1, 0), JS_FS_END }; static bool env_setProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { /* XXX porting may be easy, but these don't seem to supply setenv by default */ #if !defined SOLARIS RootedString valstr(cx); RootedString idstr(cx); int rv; RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return false; idstr = ToString(cx, idval); valstr = ToString(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) { name.clear(); value.clear(); if (!name.encodeUtf8(cx, idstr)) return false; if (!value.encodeUtf8(cx, valstr)) return false; JS_ReportErrorUTF8(cx, "can't set envariable %s to %s", name.ptr(), value.ptr()); return false; } vp.setString(valstr); #endif /* !defined SOLARIS */ return result.succeed(); } static bool env_enumerate(JSContext* cx, HandleObject obj) { static bool reflected; char** evp; char* name; char* value; RootedString valstr(cx); bool ok; if (reflected) return true; for (evp = (char**)JS_GetPrivate(obj); (name = *evp) != nullptr; evp++) { value = strchr(name, '='); if (!value) continue; *value++ = '\0'; valstr = JS_NewStringCopyZ(cx, value); ok = valstr ? JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE) : false; value[-1] = '='; if (!ok) return false; } reflected = true; return true; } static bool env_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { JSString* idstr; RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return false; idstr = ToString(cx, idval); if (!idstr) return false; JSAutoByteString name(cx, idstr); if (!name) return false; const char* value = getenv(name.ptr()); if (value) { RootedString valstr(cx, JS_NewStringCopyZ(cx, value)); if (!valstr) return false; if (!JS_DefinePropertyById(cx, obj, id, valstr, JSPROP_ENUMERATE)) { return false; } *resolvedp = true; } return true; } static const JSClassOps env_classOps = { nullptr, nullptr, nullptr, env_setProperty, env_enumerate, env_resolve }; static const JSClass env_class = { "environment", JSCLASS_HAS_PRIVATE, &env_classOps }; /***************************************************************************/ typedef enum JSShellErrNum { #define MSG_DEF(name, number, count, exception, format) \ name = number, #include "jsshell.msg" #undef MSG_DEF JSShellErr_Limit } JSShellErrNum; static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { #define MSG_DEF(name, number, count, exception, format) \ { #name, format, count } , #include "jsshell.msg" #undef MSG_DEF }; static const JSErrorFormatString* my_GetErrorMessage(void* userRef, const unsigned errorNumber) { if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) return nullptr; return &jsShell_ErrorFormatString[errorNumber]; } static bool ProcessLine(AutoJSAPI& jsapi, const char* buffer, int startline) { JSContext* cx = jsapi.cx(); JS::RootedScript script(cx); JS::RootedValue result(cx); JS::CompileOptions options(cx); options.setFileAndLine("typein", startline) .setIsRunOnce(true); if (!JS_CompileScript(cx, buffer, strlen(buffer), options, &script)) return false; if (compileOnly) return true; if (!JS_ExecuteScript(cx, script, &result)) return false; if (result.isUndefined()) return true; RootedString str(cx); if (!(str = ToString(cx, result))) return false; JSAutoByteString bytes; if (!bytes.encodeLatin1(cx, str)) return false; fprintf(gOutFile, "%s\n", bytes.ptr()); return true; } static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file, bool forceTTY) { JSContext* cx = jsapi.cx(); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); MOZ_ASSERT(global); if (forceTTY) { file = stdin; } else if (!isatty(fileno(file))) { /* * 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); JS::RootedScript script(cx); JS::RootedValue unused(cx); JS::CompileOptions options(cx); options.setUTF8(true) .setFileAndLine(filename, 1) .setIsRunOnce(true) .setNoScriptRval(true); if (!JS::Compile(cx, options, file, &script)) return false; return compileOnly || JS_ExecuteScript(cx, script, &unused); } /* It's an interactive filehandle; drop into read-eval-print loop. */ int lineno = 1; bool hitEOF = false; do { char buffer[4096]; char* 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. */ int startline = lineno; do { if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { hitEOF = true; break; } bufp += strlen(bufp); lineno++; } while (!JS_BufferIsCompilableUnit(cx, global, buffer, strlen(buffer))); if (!ProcessLine(jsapi, buffer, startline)) jsapi.ReportException(); } while (!hitEOF && !gQuitting); fprintf(gOutFile, "\n"); return true; } static bool Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) { FILE* file; if (forceTTY || !filename || strcmp(filename, "-") == 0) { file = stdin; } else { file = fopen(filename, "r"); if (!file) { /* * Use Latin1 variant here because the encoding of the return value * of strerror function can be non-UTF-8. */ JS_ReportErrorNumberLatin1(jsapi.cx(), my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN, filename, strerror(errno)); gExitCode = EXITCODE_FILE_NOT_FOUND; return false; } } bool ok = ProcessFile(jsapi, filename, file, forceTTY); if (file != stdin) fclose(file); return ok; } static int usage() { fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); fprintf(gErrFile, "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCSsmIp] [-v version] [-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n"); return 2; } static bool printUsageAndSetExitCode() { gExitCode = usage(); return false; } 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': ContextOptionsRef(cx).toggleWerror(); MOZ_FALLTHROUGH; // because -S implies -s case 's': ContextOptionsRef(cx).toggleExtraWarnings(); break; case 'I': ContextOptionsRef(cx).toggleIon() .toggleAsmJS() .toggleWasm(); break; } } } static bool ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc, XPCShellDirProvider* aDirProvider) { JSContext* cx = jsapi.cx(); const char rcfilename[] = "xpcshell.js"; FILE* rcfile; int rootPosition; JS::Rooted argsObj(cx); char* filename = nullptr; bool isInteractive = true; bool forceTTY = false; rcfile = fopen(rcfilename, "r"); if (rcfile) { printf("[loading '%s'...]\n", rcfilename); bool ok = ProcessFile(jsapi, rcfilename, rcfile, false); fclose(rcfile); if (!ok) { return false; } } JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); /* * 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. * First of all, find out the first argument position which will be passed * as a script file to be executed. */ for (rootPosition = 0; rootPosition < argc; rootPosition++) { if (argv[rootPosition][0] != '-' || argv[rootPosition][1] == '\0') { ++rootPosition; break; } bool isPairedFlag = argv[rootPosition][0] != '\0' && (argv[rootPosition][1] == 'v' || argv[rootPosition][1] == 'f' || argv[rootPosition][1] == 'e'); if (isPairedFlag && rootPosition < argc - 1) { ++rootPosition; // Skip over the 'foo' portion of |-v foo|, |-f foo|, or |-e foo|. } } /* * 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); if (!argsObj) return 1; if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) return 1; for (int j = 0, length = argc - rootPosition; j < length; j++) { RootedString str(cx, JS_NewStringCopyZ(cx, argv[rootPosition++])); if (!str || !JS_DefineElement(cx, argsObj, j, str, JSPROP_ENUMERATE)) { return 1; } } for (int 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 printUsageAndSetExitCode(); } 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': /* This used to try to turn on the debugger. */ break; case 'm': break; case 'f': if (++i == argc) { return printUsageAndSetExitCode(); } if (!Process(jsapi, argv[i], false)) return 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': { RootedValue rval(cx); if (++i == argc) { return printUsageAndSetExitCode(); } JS::CompileOptions opts(cx); opts.setFileAndLine("-e", 1); JS::Evaluate(cx, opts, argv[i], strlen(argv[i]), &rval); isInteractive = false; break; } case 'C': compileOnly = true; isInteractive = false; break; case 'S': case 's': case 'I': // 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 printUsageAndSetExitCode(); } aDirProvider->SetPluginDir(pluginsDir); break; } default: return printUsageAndSetExitCode(); } } if (filename || isInteractive) return Process(jsapi, filename, forceTTY); return true; } /***************************************************************************/ // #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_ISUPPORTS(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_ISUPPORTS(nsXPCFunctionThisTranslator, nsIXPCFunctionThisTranslator) nsXPCFunctionThisTranslator::nsXPCFunctionThisTranslator() { } nsXPCFunctionThisTranslator::~nsXPCFunctionThisTranslator() { } NS_IMETHODIMP nsXPCFunctionThisTranslator::TranslateThis(nsISupports* aInitialThis, nsISupports** _retval) { nsCOMPtr temp = aInitialThis; temp.forget(_retval); return NS_OK; } #endif 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, nullptr); 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) { cwd.SetLength(bufsize); 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 XRE_XPCShellMain(int argc, char** argv, char** envp, const XREShellData* aShellData) { MOZ_ASSERT(aShellData); JSContext* cx; int result = 0; nsresult rv; gErrFile = stderr; gOutFile = stdout; gInFile = stdin; NS_LogInit(); mozilla::LogModule::Init(); // A initializer to initialize histogram collection // used by telemetry. UniquePtr telStats = MakeUnique(); if (PR_GetEnv("MOZ_CHAOSMODE")) { ChaosFeature feature = ChaosFeature::Any; long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16); if (featureInt) { // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature. feature = static_cast(featureInt); } ChaosMode::SetChaosFeature(feature); } if (ChaosMode::isActive(ChaosFeature::Any)) { printf_stderr("*** You are running in chaos test mode. See ChaosMode.h. ***\n"); } 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); nsCOMPtr greDir; if (argc > 1 && !strcmp(argv[1], "-g")) { if (argc < 3) return usage(); rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir)); if (NS_FAILED(rv)) { printf("Couldn't use given GRE dir.\n"); return 1; } dirprovider.SetGREDirs(greDir); argc -= 2; argv += 2; } else { #ifdef XP_MACOSX // On OSX, the GreD needs to point to Contents/Resources in the .app // bundle. Libraries will be loaded at a relative path to GreD, i.e. // ../MacOS. nsCOMPtr tmpDir; XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir)); greDir->GetParent(getter_AddRefs(tmpDir)); tmpDir->Clone(getter_AddRefs(greDir)); tmpDir->SetNativeLeafName(NS_LITERAL_CSTRING("Resources")); bool dirExists = false; tmpDir->Exists(&dirExists); if (dirExists) { greDir = tmpDir.forget(); } dirprovider.SetGREDirs(greDir); #else nsAutoString workingDir; if (!GetCurrentWorkingDirectory(workingDir)) { printf("GetCurrentWorkingDirectory failed.\n"); return 1; } rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir)); if (NS_FAILED(rv)) { printf("NS_NewLocalFile failed.\n"); return 1; } #endif } 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_APP_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; } // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors // to stderr. Preferences::SetBool("browser.dom.window.dump.enabled", true); AutoJSAPI jsapi; jsapi.Init(); cx = jsapi.cx(); // Override the default XPConnect interrupt callback. We could store the // old one and restore it before shutting down, but there's not really a // reason to bother. sScriptedInterruptCallback = new PersistentRootedValue; sScriptedInterruptCallback->init(cx, UndefinedValue()); JS_AddInterruptCallback(cx, XPCShellInterruptCallback); 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(cx); MOZ_ASSERT(scb, "We are assuming that nsScriptSecurityManager::Init() has been run"); shellSecurityCallbacks = *scb; JS_SetSecurityCallbacks(cx, &shellSecurityCallbacks); #ifdef TEST_TranslateThis nsCOMPtr translator(new nsXPCFunctionThisTranslator); xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator); #endif RefPtr backstagePass; rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); if (NS_FAILED(rv)) { fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n", static_cast(rv)); return 1; } // Make the default XPCShell global use a fresh zone (rather than the // System Zone) to improve cross-zone test coverage. JS::CompartmentOptions options; options.creationOptions().setZone(JS::FreshZone); if (xpc::SharedMemoryEnabled()) options.creationOptions().setSharedMemoryAndAtomicsEnabled(true); options.behaviors().setVersion(JSVERSION_LATEST); nsCOMPtr holder; rv = xpc->InitClassesWithNewWrappedGlobal(cx, static_cast(backstagePass), systemprincipal, 0, options, getter_AddRefs(holder)); if (NS_FAILED(rv)) return 1; // Initialize graphics prefs on the main thread, if not already done gfxPrefs::GetSingleton(); // Initialize e10s check on the main thread, if not already done BrowserTabsRemoteAutostart(); #ifdef XP_WIN // Plugin may require audio session if installed plugin can initialize // asynchronized. AutoAudioSession audioSession; #endif { JS::Rooted glob(cx, holder->GetJSObject()); if (!glob) { return 1; } // Even if we're building in a configuration where source is // discarded, there's no reason to do that on XPCShell, and doing so // might break various automation scripts. JS::CompartmentBehaviorsRef(glob).setDiscardSource(false); backstagePass->SetGlobalObject(glob); JSAutoCompartment ac(cx, glob); if (!JS_InitReflectParse(cx, glob)) { return 1; } if (!JS_DefineFunctions(cx, glob, glob_functions) || !JS_DefineProfilingFunctions(cx, glob)) { return 1; } JS::Rooted envobj(cx); envobj = JS_DefineObject(cx, glob, "environment", &env_class); if (!envobj) { return 1; } JS_SetPrivate(envobj, envp); nsAutoString workingDirectory; if (GetCurrentWorkingDirectory(workingDirectory)) gWorkingDirectory = &workingDirectory; JS_DefineProperty(cx, glob, "__LOCATION__", JS::UndefinedHandleValue, JSPROP_SHARED, GetLocationProperty, nullptr); { // We are almost certainly going to run script here, so we need an // AutoEntryScript. This is Gecko-specific and not in any spec. AutoEntryScript aes(backstagePass, "xpcshell argument processing"); // If an exception is thrown, we'll set our return code // appropriately, and then let the AutoEntryScript destructor report // the error to the console. if (!ProcessArgs(aes, argv, argc, &dirprovider)) { if (gExitCode) { result = gExitCode; } else if (gQuitting) { result = 0; } else { result = EXITCODE_RUNTIME_ERROR; } } } JS_DropPrincipals(cx, gJSPrincipals); JS_SetAllNonReservedSlotsToUndefined(cx, glob); JS_SetAllNonReservedSlotsToUndefined(cx, JS_GlobalLexicalEnvironment(glob)); JS_GC(cx); } JS_GC(cx); } // this scopes the nsCOMPtrs if (!XRE_ShutdownTestShell()) NS_ERROR("problem shutting down testshell"); // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM rv = NS_ShutdownXPCOM( nullptr ); MOZ_ASSERT(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 telStats = nullptr; appDir = nullptr; appFile = nullptr; dirprovider.ClearGREDirs(); dirprovider.ClearAppDir(); dirprovider.ClearPluginDir(); dirprovider.ClearAppFile(); NS_LogTerm(); return result; } void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) { mGREDir = greDir; mGREDir->Clone(getter_AddRefs(mGREBinDir)); #ifdef XP_MACOSX nsAutoCString leafName; mGREDir->GetNativeLeafName(leafName); if (leafName.Equals("Resources")) { mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); } #endif } void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; } void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; } void XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) { mPluginDir = pluginDir; } NS_IMETHODIMP_(MozExternalRefCountType) XPCShellDirProvider::AddRef() { return 2; } NS_IMETHODIMP_(MozExternalRefCountType) XPCShellDirProvider::Release() { return 1; } NS_IMPL_QUERY_INTERFACE(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 (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) { *persistent = true; return mGREBinDir->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; file.forget(result); return NS_OK; } 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)) { nsCOMArray dirs; // Add the test plugin location passed in by the caller or through // runxpcshelltests. if (mPluginDir) { dirs.AppendObject(mPluginDir); // If there was no path specified, default to the one set up by automation } else { nsCOMPtr file; 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); } } } } return NS_NewArrayEnumerator(result, dirs); } return NS_ERROR_FAILURE; }