summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCShellImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/XPCShellImpl.cpp')
-rw-r--r--js/xpconnect/src/XPCShellImpl.cpp1765
1 files changed, 1765 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp
new file mode 100644
index 000000000..d86b5c5d3
--- /dev/null
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -0,0 +1,1765 @@
+/* -*- 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 <android/log.h>
+#endif
+
+#ifdef XP_WIN
+#include "mozilla/widget/AudioSession.h"
+#include <windows.h>
+#if defined(MOZ_SANDBOX)
+#include "SandboxBroker.h"
+#endif
+#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 MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#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<nsIFile> mGREDir;
+ nsCOMPtr<nsIFile> mGREBinDir;
+ nsCOMPtr<nsIFile> mAppDir;
+ nsCOMPtr<nsIFile> mPluginDir;
+ nsCOMPtr<nsIFile> 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<nsIXPConnect> 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<nsIFile> 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<JSObject*> 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<JSScript*> script(cx);
+ JS::Rooted<JSObject*> 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<nsIXPConnect> 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;
+}
+
+#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.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<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;
+}
+
+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),
+#ifdef JS_GC_ZEAL
+ JS_FS("gczeal", GCZeal, 1,0),
+#endif
+ 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 <s.shanmuganathan@digital.com>, 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<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 '#'. 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<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 '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<nsIFile> 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<nsISupports> 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<base::StatisticsRecorder> telStats =
+ MakeUnique<base::StatisticsRecorder>();
+
+ 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");
+ }
+
+ nsCOMPtr<nsIFile> appFile;
+ rv = XRE_GetBinaryPath(argv[0], 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;
+ }
+
+ XPCShellDirProvider dirprovider;
+
+ 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(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<nsIFile> 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<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;
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ const char* val = getenv("MOZ_CRASHREPORTER");
+ if (val && *val) {
+ rv = CrashReporter::SetExceptionHandler(greDir, true);
+ if (NS_FAILED(rv)) {
+ printf("CrashReporter::SetExceptionHandler failed!\n");
+ return 1;
+ }
+ MOZ_ASSERT(CrashReporter::GetEnabled());
+ }
+#endif
+
+ {
+ if (argc > 1 && !strcmp(argv[1], "--greomni")) {
+ nsCOMPtr<nsIFile> greOmni;
+ nsCOMPtr<nsIFile> 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<nsIServiceManager> 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<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
+ if (!xpc) {
+ printf("failed to get nsXPConnect service!\n");
+ return 1;
+ }
+
+ 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);
+
+#ifdef TEST_TranslateThis
+ nsCOMPtr<nsIXPCFunctionThisTranslator>
+ translator(new nsXPCFunctionThisTranslator);
+ xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator);
+#endif
+
+ RefPtr<BackstagePass> backstagePass;
+ rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
+ if (NS_FAILED(rv)) {
+ fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n",
+ static_cast<uint32_t>(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<nsIXPConnectJSObjectHolder> holder;
+ rv = xpc->InitClassesWithNewWrappedGlobal(cx,
+ static_cast<nsIGlobalObject*>(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;
+
+#if defined(MOZ_SANDBOX)
+ // Required for sandboxed child processes.
+ if (aShellData->sandboxBrokerServices) {
+ SandboxBroker::Initialize(aShellData->sandboxBrokerServices);
+ } else {
+ NS_WARNING("Failed to initialize broker services, sandboxed "
+ "processes will fail to start.");
+ }
+#endif
+#endif
+
+ {
+ JS::Rooted<JSObject*> 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<JSObject*> 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();
+
+#ifdef MOZ_CRASHREPORTER
+ // Shut down the crashreporter service to prevent leaking some strings it holds.
+ if (CrashReporter::GetEnabled())
+ CrashReporter::UnsetExceptionHandler();
+#endif
+
+ 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<nsIFile> 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<nsIFile> dirs;
+
+ nsCOMPtr<nsIFile> 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<nsIFile> dirs;
+ nsCOMPtr<nsIFile> 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<nsIFile> 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<nsIFile> 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;
+}