summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCShellImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/xpconnect/src/XPCShellImpl.cpp1539
1 files changed, 1539 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp
new file mode 100644
index 0000000000..124c2ed37d
--- /dev/null
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -0,0 +1,1539 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#include "nsXULAppAPI.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/CallAndConstruct.h" // JS_CallFunctionValue
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/ContextOptions.h"
+#include "js/Printf.h"
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunctions, JS_DefineProperty
+#include "js/PropertySpec.h"
+#include "js/SourceText.h" // JS::SourceText
+#include "mozilla/ChaosMode.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsExceptionHandler.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 "nsJSUtils.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+#include "BackstagePass.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsJSUtils.h"
+
+#include "nsIXULRuntime.h"
+#include "nsIAppStartup.h"
+#include "Components.h"
+#include "ProfilerControl.h"
+
+#ifdef ANDROID
+# include <android/log.h>
+# include "XREShellData.h"
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/mscom/ProcessRuntime.h"
+# include "mozilla/ScopeExit.h"
+# include "mozilla/widget/AudioSession.h"
+# include "mozilla/WinDllServices.h"
+# include "mozilla/WindowsBCryptInitialization.h"
+# include <windows.h>
+# if defined(MOZ_SANDBOX)
+# include "XREShellData.h"
+# include "sandboxBroker.h"
+# endif
+#endif
+
+#ifdef MOZ_CODE_COVERAGE
+# include "mozilla/CodeCoverageHandler.h"
+#endif
+
+// all this crap is needed to do the interactive shell stuff
+#include <stdlib.h>
+#include <errno.h>
+#ifdef HAVE_IO_H
+# include <io.h> /* for isatty() */
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h> /* for isatty() */
+#endif
+
+#ifdef ENABLE_TESTS
+# include "xpctest_private.h"
+#endif
+
+// Fuzzing support for XPC runtime fuzzing
+#ifdef FUZZING_INTERFACES
+# include "xpcrtfuzzing/xpcrtfuzzing.h"
+# include "XREShellData.h"
+static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
+static bool fuzzHaveModule = !!getenv("FUZZER");
+#endif // FUZZING_INTERFACES
+
+using namespace mozilla;
+using namespace JS;
+using mozilla::dom::AutoEntryScript;
+using mozilla::dom::AutoJSAPI;
+
+class XPCShellDirProvider : public nsIDirectoryServiceProvider2 {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+ XPCShellDirProvider() = default;
+ ~XPCShellDirProvider() = default;
+
+ // 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; }
+
+ private:
+ nsCOMPtr<nsIFile> mGREDir;
+ nsCOMPtr<nsIFile> mGREBinDir;
+ nsCOMPtr<nsIFile> mAppDir;
+ nsCOMPtr<nsIFile> mAppFile;
+};
+
+#ifdef XP_WIN
+class MOZ_STACK_CLASS AutoAudioSession {
+ public:
+ AutoAudioSession() { widget::StartAudioSession(); }
+
+ ~AutoAudioSession() { widget::StopAudioSession(); }
+};
+#endif
+
+#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()) {
+ NS_ConvertUTF8toUTF16 filenameString(filename.get());
+
+# if defined(XP_WIN)
+ // 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++;
+ }
+# endif
+
+ nsCOMPtr<nsIFile> location;
+ nsresult 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);
+ RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
+ rv = nsXPConnect::XPConnect()->WrapNative(
+ cx, scope, 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 */
+ JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str);
+ if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.get())) {
+ 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();
+
+#ifdef FUZZING_INTERFACES
+ if (fuzzHaveModule && !fuzzDoDebug) {
+ // When fuzzing and not debugging, suppress any print() output,
+ // as it slows down fuzzing and makes libFuzzer's output hard
+ // to read.
+ return true;
+ }
+#endif // FUZZING_INTERFACES
+
+ RootedString str(cx);
+ nsAutoCString utf8output;
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ str = ToString(cx, args[i]);
+ if (!str) {
+ return false;
+ }
+
+ JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
+ if (!utf8str) {
+ return false;
+ }
+
+ if (i) {
+ utf8output.Append(' ');
+ }
+ utf8output.Append(utf8str.get(), strlen(utf8str.get()));
+ }
+ 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;
+ }
+
+ JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
+ if (!utf8str) {
+ return false;
+ }
+
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
+#endif
+#ifdef XP_WIN
+ if (IsDebuggerPresent()) {
+ nsAutoJSString wstr;
+ if (!wstr.init(cx, str)) {
+ return false;
+ }
+ OutputDebugStringW(wstr.get());
+ }
+#endif
+ fputs(utf8str.get(), gOutFile);
+ fflush(gOutFile);
+ return true;
+}
+
+static bool Load(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::RootedObject thisObject(cx);
+ if (!args.computeThis(cx, &thisObject)) {
+ return false;
+ }
+ if (!JS_IsGlobalObject(thisObject)) {
+ 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;
+ }
+ JS::UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
+ if (!filename) {
+ return false;
+ }
+ JS::CompileOptions options(cx);
+ options.setIsRunOnce(true).setSkipFilenameValidation(true);
+ JS::Rooted<JSScript*> script(
+ cx, JS::CompileUtf8Path(cx, options, filename.get()));
+ if (!script) {
+ return false;
+ }
+
+ if (!compileOnly) {
+ if (!JS_ExecuteScript(cx, script)) {
+ return false;
+ }
+ }
+ }
+ args.rval().setUndefined();
+ 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;
+ }
+ }
+
+ nsXPConnect::XPConnect()->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;
+}
+
+#ifdef JS_GC_ZEAL
+static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ uint32_t zeal;
+ if (!ToUint32(cx, args.get(0), &zeal)) {
+ return false;
+ }
+
+ JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+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.get(1).isObject() && !JS_ObjectIsFunction(&args[1].toObject())) {
+ 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);
+ JS::UniqueChars opt;
+ for (unsigned i = 0; i < args.length(); ++i) {
+ str = ToString(cx, args[i]);
+ if (!str) {
+ return false;
+ }
+
+ opt = JS_EncodeStringToUTF8(cx, str);
+ if (!opt) {
+ return false;
+ }
+
+ if (strcmp(opt.get(), "strict_mode") == 0) {
+ ContextOptionsRef(cx).toggleStrictMode();
+ } else {
+ JS_ReportErrorUTF8(cx,
+ "unknown option name '%s'. The valid name is "
+ "strict_mode.",
+ opt.get());
+ return false;
+ }
+ }
+
+ UniqueChars names;
+ if (names && oldContextOptions.strictMode()) {
+ names = JS_sprintf_append(std::move(names), "%s%s", names ? "," : "",
+ "strict_mode");
+ if (!names) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ str = JS_NewStringCopyZ(cx, names.get());
+ 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;
+ }
+
+ MOZ_ASSERT(js::IsFunctionObject(&callback.toObject()));
+
+ JSAutoRealm ar(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 function object.
+ if (!args[0].isObject() || !js::IsFunctionObject(&args[0].toObject())) {
+ JS_ReportErrorASCII(cx, "Argument must be a function");
+ return false;
+ }
+
+ *sScriptedInterruptCallback = args[0];
+
+ return true;
+}
+
+static bool SimulateNoScriptActivity(JSContext* cx, unsigned argc, Value* vp) {
+ // Sanity-check args.
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
+ JS_ReportErrorASCII(cx, "Expected a positive integer argument");
+ return false;
+ }
+
+ // This mimics mozilla::SpinEventLoopUntil but instead of spinning the
+ // event loop we sleep, to make sure we don't run script.
+ xpc::AutoScriptActivity asa(false);
+ PR_Sleep(PR_SecondsToInterval(args[0].toInt32()));
+
+ args.rval().setUndefined();
+ 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<JSObject*> arg1(cx, &args[0].toObject());
+ nsCOMPtr<nsIFile> 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;
+}
+
+#ifdef ANDROID
+static bool ChangeTestShellDir(JSContext* cx, unsigned argc, Value* vp) {
+ // This method should only be used by testing/xpcshell/head.js to change to
+ // the correct directory on Android Remote XPCShell tests.
+ //
+ // TODO: Bug 1801725 - Find a more ergonomic way to do this than exposing
+ // identical methods in XPCShellEnvironment and XPCShellImpl to chdir on
+ // android for Remote XPCShell tests on Android.
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "changeTestShellDir() takes one argument");
+ return false;
+ }
+
+ nsAutoJSCString path;
+ if (!path.init(cx, args[0])) {
+ JS_ReportErrorASCII(
+ cx, "changeTestShellDir(): could not convert argument 1 to string");
+ return false;
+ }
+
+ if (chdir(path.get())) {
+ JS_ReportErrorASCII(cx, "changeTestShellDir(): could not change directory");
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+#ifdef ENABLE_TESTS
+static bool RegisterXPCTestComponents(JSContext* cx, unsigned argc, Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (args.length() != 0) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+ nsresult rv = xpcTestRegisterComponents();
+ if (NS_FAILED(rv)) {
+ XPCThrower::Throw(rv, cx);
+ return false;
+ }
+ return true;
+}
+#endif
+
+static const JSFunctionSpec glob_functions[] = {
+ // clang-format off
+ JS_FN("print", Print, 0,0),
+ JS_FN("readline", ReadLine, 1,0),
+ JS_FN("load", Load, 1,0),
+ JS_FN("quit", Quit, 0,0),
+ JS_FN("dumpXPC", DumpXPC, 1,0),
+ JS_FN("dump", Dump, 1,0),
+ JS_FN("gc", GC, 0,0),
+#ifdef JS_GC_ZEAL
+ JS_FN("gczeal", GCZeal, 1,0),
+#endif
+ JS_FN("options", Options, 0,0),
+ JS_FN("sendCommand", SendCommand, 1,0),
+ JS_FN("atob", xpc::Atob, 1,0),
+ JS_FN("btoa", xpc::Btoa, 1,0),
+ JS_FN("setInterruptCallback", SetInterruptCallback, 1,0),
+ JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity, 1,0),
+ JS_FN("registerAppManifest", RegisterAppManifest, 1, 0),
+#ifdef ANDROID
+ JS_FN("changeTestShellDir", ChangeTestShellDir, 1,0),
+#endif
+#ifdef ENABLE_TESTS
+ JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0),
+#endif
+ JS_FS_END
+ // clang-format on
+};
+
+/***************************************************************************/
+
+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 ProcessUtf8Line(AutoJSAPI& jsapi, const char* buffer,
+ int startline) {
+ JSContext* cx = jsapi.cx();
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("typein", startline)
+ .setIsRunOnce(true)
+ .setSkipFilenameValidation(true);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, buffer, strlen(buffer), JS::SourceOwnership::Borrowed)) {
+ return false;
+ }
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ if (!script) {
+ return false;
+ }
+ if (compileOnly) {
+ return true;
+ }
+
+ JS::RootedValue result(cx);
+ if (!JS_ExecuteScript(cx, script, &result)) {
+ return false;
+ }
+
+ if (result.isUndefined()) {
+ return true;
+ }
+
+ RootedString str(cx, JS::ToString(cx, result));
+ if (!str) {
+ return false;
+ }
+
+ JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
+ if (!bytes) {
+ return false;
+ }
+
+ fprintf(gOutFile, "%s\n", bytes.get());
+ return true;
+}
+
+static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file,
+ bool forceTTY) {
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> 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 '#'.
+ */
+ int ch = fgetc(file);
+ if (ch == '#') {
+ while ((ch = fgetc(file)) != EOF) {
+ if (ch == '\n' || ch == '\r') {
+ break;
+ }
+ }
+ }
+ ungetc(ch, file);
+
+ JS::UniqueChars filenameUtf8 = JS::EncodeNarrowToUtf8(jsapi.cx(), filename);
+ if (!filenameUtf8) {
+ return false;
+ }
+
+ JS::RootedScript script(cx);
+ JS::RootedValue unused(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filenameUtf8.get(), 1)
+ .setIsRunOnce(true)
+ .setNoScriptRval(true)
+ .setSkipFilenameValidation(true);
+ script = JS::CompileUtf8File(cx, options, file);
+ if (!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_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
+
+ if (!ProcessUtf8Line(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]... [-WwxiCmIp] "
+ "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n");
+ return 2;
+}
+
+static bool printUsageAndSetExitCode() {
+ gExitCode = usage();
+ return false;
+}
+
+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<JSObject*> 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<JSObject*> 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 <file> 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 '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.setSkipFilenameValidation(true);
+ opts.setFileAndLine("-e", 1);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ if (srcBuf.init(cx, argv[i], strlen(argv[i]),
+ JS::SourceOwnership::Borrowed)) {
+ JS::Evaluate(cx, opts, srcBuf, &rval);
+ }
+
+ isInteractive = false;
+ break;
+ }
+ case 'C':
+ compileOnly = true;
+ isInteractive = false;
+ break;
+ default:
+ return printUsageAndSetExitCode();
+ }
+ }
+
+ if (filename || isInteractive) {
+ return Process(jsapi, filename, forceTTY);
+ }
+ 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, 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, '/');
+ CopyUTF8toUTF16(cwd, workingDirectory);
+#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;
+
+#ifdef ANDROID
+ gOutFile = aShellData->outFile;
+ gErrFile = aShellData->errFile;
+#else
+ gOutFile = stdout;
+ gErrFile = stderr;
+#endif
+ gInFile = stdin;
+
+ NS_LogInit();
+
+ mozilla::LogModule::Init(argc, argv);
+
+ // This guard ensures that all threads that attempt to register themselves
+ // with the IOInterposer will be properly tracked.
+ mozilla::IOInterposerInit ioInterposerGuard;
+
+ XRE_InitCommandLine(argc, argv);
+
+ char aLocal;
+ profiler_init(&aLocal);
+
+#ifdef MOZ_ASAN_REPORTER
+ PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1");
+#endif
+
+ 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<ChaosFeature>(featureInt);
+ }
+ ChaosMode::SetChaosFeature(feature);
+ }
+
+ if (ChaosMode::isActive(ChaosFeature::Any)) {
+ printf_stderr(
+ "*** You are running in chaos test mode. See ChaosMode.h. ***\n");
+ }
+
+#ifdef XP_WIN
+ // Some COM settings are global to the process and must be set before any non-
+ // trivial COM is run in the application. Since these settings may affect
+ // stability, we should instantiate COM ASAP so that we can ensure that these
+ // global settings are configured before anything can interfere.
+ mscom::ProcessRuntime mscom;
+#endif
+
+ // The provider needs to outlive the call to shutting down XPCOM.
+ XPCShellDirProvider dirprovider;
+
+ { // Start scoping nsCOMPtrs
+ nsCOMPtr<nsIFile> appFile;
+ rv = XRE_GetBinaryPath(getter_AddRefs(appFile));
+ if (NS_FAILED(rv)) {
+ printf("Couldn't find application file.\n");
+ return 1;
+ }
+ nsCOMPtr<nsIFile> appDir;
+ rv = appFile->GetParent(getter_AddRefs(appDir));
+ if (NS_FAILED(rv)) {
+ printf("Couldn't get application directory.\n");
+ return 1;
+ }
+
+ dirprovider.SetAppFile(appFile);
+
+ nsCOMPtr<nsIFile> 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<nsIFile> tmpDir;
+ XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir));
+ greDir->GetParent(getter_AddRefs(tmpDir));
+ tmpDir->Clone(getter_AddRefs(greDir));
+ tmpDir->SetNativeLeafName("Resources"_ns);
+ 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<nsIFile> dir;
+ rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir));
+ if (NS_SUCCEEDED(rv)) {
+ appDir = dir;
+ 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<nsIFile> 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;
+ }
+
+ const char* val = getenv("MOZ_CRASHREPORTER");
+ if (val && *val && !CrashReporter::IsDummy()) {
+ rv = CrashReporter::SetExceptionHandler(greDir, true);
+ if (NS_FAILED(rv)) {
+ printf("CrashReporter::SetExceptionHandler failed!\n");
+ return 1;
+ }
+ MOZ_ASSERT(CrashReporter::GetEnabled());
+ }
+
+ if (argc > 1 && !strcmp(argv[1], "--greomni")) {
+ nsCOMPtr<nsIFile> greOmni;
+ XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni));
+ XRE_InitOmnijar(greOmni, greOmni);
+ argc -= 2;
+ argv += 2;
+ }
+
+ rv = NS_InitXPCOM(nullptr, appDir, &dirprovider);
+ if (NS_FAILED(rv)) {
+ printf("NS_InitXPCOM failed!\n");
+ return 1;
+ }
+
+ // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors
+ // to stderr.
+ Preferences::SetBool("browser.dom.window.dump.enabled", true);
+ Preferences::SetBool("devtools.console.stdout.chrome", 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++;
+
+ nsCOMPtr<nsIPrincipal> 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<nsIScriptSecurityManager> 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);
+
+ auto backstagePass = MakeRefPtr<BackstagePass>();
+
+ // Make the default XPCShell global use a fresh zone (rather than the
+ // System Zone) to improve cross-zone test coverage.
+ JS::RealmOptions options;
+ options.creationOptions().setNewCompartmentAndZone();
+ xpc::SetPrefableRealmOptions(options);
+
+ // 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.
+ options.behaviors().setDiscardSource(false);
+
+ JS::Rooted<JSObject*> glob(cx);
+ rv = xpc::InitClassesWithNewWrappedGlobal(
+ cx, static_cast<nsIGlobalObject*>(backstagePass), systemprincipal, 0,
+ options, &glob);
+ if (NS_FAILED(rv)) {
+ return 1;
+ }
+
+ // Initialize e10s check on the main thread, if not already done
+ BrowserTabsRemoteAutostart();
+#if defined(XP_WIN)
+ // Plugin may require audio session if installed plugin can initialize
+ // asynchronized.
+ AutoAudioSession audioSession;
+
+ // Ensure that DLL Services are running
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor(true);
+ auto dllServicesDisable =
+ MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); });
+
+# if defined(MOZ_SANDBOX)
+ // Required for sandboxed child processes.
+ if (aShellData->sandboxBrokerServices) {
+ SandboxBroker::Initialize(aShellData->sandboxBrokerServices);
+ SandboxBroker::GeckoDependentInitialize();
+ } else {
+ NS_WARNING(
+ "Failed to initialize broker services, sandboxed "
+ "processes will fail to start.");
+ }
+# endif // defined(MOZ_SANDBOX)
+
+ {
+ DebugOnly<bool> result = WindowsBCryptInitialization();
+ MOZ_ASSERT(result);
+ }
+#endif // defined(XP_WIN)
+
+#ifdef MOZ_CODE_COVERAGE
+ CodeCoverageHandler::Init();
+#endif
+
+ {
+ if (!glob) {
+ return 1;
+ }
+
+ nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
+ if (!appStartup) {
+ return 1;
+ }
+ appStartup->DoneStartingUp();
+
+ backstagePass->SetGlobalObject(glob);
+
+ JSAutoRealm ar(cx, glob);
+
+ if (!JS_InitReflectParse(cx, glob)) {
+ return 1;
+ }
+
+ if (!JS_DefineFunctions(cx, glob, glob_functions)) {
+ return 1;
+ }
+
+ nsAutoString workingDirectory;
+ if (GetCurrentWorkingDirectory(workingDirectory)) {
+ gWorkingDirectory = &workingDirectory;
+ }
+
+ JS_DefineProperty(cx, glob, "__LOCATION__", GetLocationProperty, nullptr,
+ 0);
+
+ {
+#ifdef FUZZING_INTERFACES
+ if (fuzzHaveModule) {
+# ifdef LIBFUZZER
+ // argv[0] was removed previously, but libFuzzer expects it
+ argc++;
+ argv--;
+
+ result = FuzzXPCRuntimeStart(&jsapi, &argc, &argv,
+ aShellData->fuzzerDriver);
+# elif AFLFUZZ
+ MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration");
+# endif
+ } else {
+#endif
+ // 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;
+ }
+ }
+#ifdef FUZZING_INTERFACES
+ }
+#endif
+ }
+
+ // Signal that we're now shutting down.
+ nsCOMPtr<nsIObserver> obs = do_QueryInterface(appStartup);
+ if (obs) {
+ obs->Observe(nullptr, "quit-application-forced", nullptr);
+ }
+
+ JS_DropPrincipals(cx, gJSPrincipals);
+ JS_SetAllNonReservedSlotsToUndefined(glob);
+ JS::RootedObject lexicalEnv(cx, JS_GlobalLexicalEnvironment(glob));
+ JS_SetAllNonReservedSlotsToUndefined(lexicalEnv);
+ JS_GC(cx);
+ }
+ JS_GC(cx);
+
+ dirprovider.ClearGREDirs();
+ dirprovider.ClearAppDir();
+ dirprovider.ClearAppFile();
+ } // 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");
+
+ // Shut down the crashreporter service to prevent leaking some strings it
+ // holds.
+ if (CrashReporter::GetEnabled()) {
+ CrashReporter::UnsetExceptionHandler();
+ }
+
+ // This must precede NS_LogTerm(), otherwise xpcshell return non-zero
+ // during some tests, which causes failures.
+ profiler_shutdown();
+
+ NS_LogTerm();
+
+ XRE_DeinitCommandLine();
+
+ return result;
+}
+
+void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) {
+ mGREDir = greDir;
+ mGREDir->Clone(getter_AddRefs(mGREBinDir));
+
+#ifdef XP_MACOSX
+ nsAutoCString leafName;
+ mGREDir->GetNativeLeafName(leafName);
+ if (leafName.EqualsLiteral("Resources")) {
+ mGREBinDir->SetNativeLeafName("MacOS"_ns);
+ }
+#endif
+}
+
+void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; }
+
+void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; }
+
+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<nsIFile> file;
+ *persistent = true;
+ if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) ||
+ NS_FAILED(file->AppendNative("defaults"_ns)) ||
+ NS_FAILED(file->AppendNative("pref"_ns)))
+ 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<nsIFile> dirs;
+
+ nsCOMPtr<nsIFile> file;
+ mGREDir->Clone(getter_AddRefs(file));
+ file->AppendNative("chrome"_ns);
+ 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, NS_GET_IID(nsIFile));
+ } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) {
+ nsCOMArray<nsIFile> dirs;
+ nsCOMPtr<nsIFile> appDir;
+ bool exists;
+ if (mAppDir && NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) &&
+ NS_SUCCEEDED(appDir->AppendNative("defaults"_ns)) &&
+ NS_SUCCEEDED(appDir->AppendNative("preferences"_ns)) &&
+ NS_SUCCEEDED(appDir->Exists(&exists)) && exists) {
+ dirs.AppendObject(appDir);
+ return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
+ }
+ return NS_ERROR_FAILURE;
+ }
+ return NS_ERROR_FAILURE;
+}