Mypal/js/xpconnect/src/XPCShellImpl.cpp

1700 lines
50 KiB
C++

/* -*- 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>
#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
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;
}
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),
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 XP_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 SCO
{
char* waste = JS_smprintf("%s=%s", name.ptr(), value.ptr());
if (!waste) {
JS_ReportOutOfMemory(cx);
return false;
}
rv = putenv(waste);
free(waste);
}
#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 XP_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)
{
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;
}
{
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;
#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();
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;
}