js/xpconnect/src/XPCShellImpl.cpp
author Kyle Huey <khuey@kylehuey.com>
Tue, 11 Aug 2015 06:10:46 -0700
changeset 257246 a13c1f26e351dd6251da641fe7a9eb53790fc2d0
parent 256257 65f68419dbfcefe79421876936e62c7b0a58a9df
child 260805 f0563b886a8ebb3246bd5a7ccdab4ac5a696640f
permissions -rw-r--r--
Bug 1179909: Refactor stable state handling. r=smaug This is motivated by three separate but related problems: 1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1). 2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this). 3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread. The solve this, this patch does the following: 1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime. 2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs. 3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with. 4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways: a) It fires between microtasks, which was the motivation for starting this. b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this. 5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.

/* -*- 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/dom/ScriptSettings.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 "nsAutoPtr.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 <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

#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;
};

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_ReportError(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(JS_GetRuntime(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_ReportError(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) {
            JS_ReportError(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);
    JSRuntime* rt = JS_GetRuntime(cx);
    JS_GC(rt);
#ifdef JS_GCMETER
    js_DumpGCStats(rt, stdout);
#endif
    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_ReportError(cx, "Function takes at least one argument!");
        return false;
    }

    RootedString str(cx, ToString(cx, args[0]));
    if (!str) {
        JS_ReportError(cx, "Could not convert argument 1 to string!");
        return false;
    }

    if (args.length() > 1 && JS_TypeOfValue(cx, args[1]) != JSTYPE_FUNCTION) {
        JS_ReportError(cx, "Could not convert argument 2 to function!");
        return false;
    }

    if (!XRE_SendTestShellCommand(cx, str, args.length() > 1 ? args[1].address() : nullptr)) {
        JS_ReportError(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);
    RuntimeOptions oldRuntimeOptions = RuntimeOptionsRef(cx);

    for (unsigned i = 0; i < args.length(); ++i) {
        JSString* str = ToString(cx, args[i]);
        if (!str)
            return false;

        JSAutoByteString opt(cx, str);
        if (!opt)
            return false;

        if (strcmp(opt.ptr(), "strict") == 0)
            RuntimeOptionsRef(cx).toggleExtraWarnings();
        else if (strcmp(opt.ptr(), "werror") == 0)
            RuntimeOptionsRef(cx).toggleWerror();
        else if (strcmp(opt.ptr(), "strict_mode") == 0)
            RuntimeOptionsRef(cx).toggleStrictMode();
        else {
            JS_ReportError(cx, "unknown option name '%s'. The valid names are "
                           "strict, werror, and strict_mode.", opt.ptr());
            return false;
        }
    }

    char* names = nullptr;
    if (oldRuntimeOptions.extraWarnings()) {
        names = JS_sprintf_append(names, "%s", "strict");
        if (!names) {
            JS_ReportOutOfMemory(cx);
            return false;
        }
    }
    if (oldRuntimeOptions.werror()) {
        names = JS_sprintf_append(names, "%s%s", names ? "," : "", "werror");
        if (!names) {
            JS_ReportOutOfMemory(cx);
            return false;
        }
    }
    if (names && oldRuntimeOptions.strictMode()) {
        names = JS_sprintf_append(names, "%s%s", names ? "," : "", "strict_mode");
        if (!names) {
            JS_ReportOutOfMemory(cx);
            return false;
        }
    }

    JSString* str = JS_NewStringCopyZ(cx, names);
    free(names);
    if (!str)
        return false;

    args.rval().setString(str);
    return true;
}

static bool
Atob(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (!args.length())
        return true;

    return xpc::Base64Decode(cx, args[0], args.rval());
}

static bool
Btoa(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (!args.length())
        return true;

  return xpc::Base64Encode(cx, args[0], args.rval());
}

static PersistentRootedValue sScriptedInterruptCallback;

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_ReportError(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_ReportError(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_ReportError(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_ReportError(cx, "Wrong number of arguments");
        return false;
    }
    if (!args[0].isObject()) {
        JS_ReportError(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",            Atob,           1,0),
    JS_FS("btoa",            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
    JSString* valstr;
    JS::Rooted<JSString*> 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) {
        JS_ReportError(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 JSClass env_class = {
    "environment", JSCLASS_HAS_PRIVATE,
    nullptr, nullptr, nullptr, env_setProperty,
    env_enumerate, env_resolve
};

/***************************************************************************/

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) \
    { 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.ClearException(); // Errors from interactive processing are squelched.
    } 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) {
            JS_ReportErrorNumber(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':
            RuntimeOptionsRef(cx).toggleWerror();
        case 's':
            RuntimeOptionsRef(cx).toggleExtraWarnings();
            break;
        case 'I':
            RuntimeOptionsRef(cx).toggleIon()
                                 .toggleAsmJS();
            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)
{
    JSRuntime* rt;
    JSContext* cx;
    int result = 0;
    nsresult rv;

    gErrFile = stderr;
    gOutFile = stdout;
    gInFile = stdin;

    NS_LogInit();

    // A initializer to initialize histogram collection
    // used by telemetry.
    UniquePtr<base::StatisticsRecorder> telStats =
       MakeUnique<base::StatisticsRecorder>();

    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;
        }

        rt = xpc::GetJSRuntime();
        if (!rt) {
            printf("failed to get JSRuntime from XPConnect!\n");
            return 1;
        }

        // 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.init(rt, UndefinedValue());
        JS_SetInterruptCallback(rt, XPCShellInterruptCallback);

        AutoJSAPI jsapi;
        jsapi.Init();
        cx = jsapi.cx();

        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(rt);
        MOZ_ASSERT(scb, "We are assuming that nsScriptSecurityManager::Init() has been run");
        shellSecurityCallbacks = *scb;
        JS_SetSecurityCallbacks(rt, &shellSecurityCallbacks);

#ifdef TEST_TranslateThis
        nsCOMPtr<nsIXPCFunctionThisTranslator>
            translator(new nsXPCFunctionThisTranslator);
        xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator);
#endif

        nsRefPtr<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.setZone(JS::FreshZone)
               .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();

        {
            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::CompartmentOptionsRef(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 AutoJSAPI destructor report
                // the error to the console.
                aes.TakeOwnershipOfErrorReporting();
                if (!ProcessArgs(aes, argv, argc, &dirprovider)) {
                    if (gExitCode) {
                        result = gExitCode;
                    } else if (gQuitting) {
                        result = 0;
                    } else {
                        result = EXITCODE_RUNTIME_ERROR;
                    }
                }
            }

            JS_DropPrincipals(rt, gJSPrincipals);
            JS_SetAllNonReservedSlotsToUndefined(cx, glob);
            JS_GC(rt);
        }
        JS_GC(rt);
    } // 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;
}