js/xpconnect/src/Sandbox.cpp
author Nicholas Nethercote <nnethercote@mozilla.com>
Mon, 23 Jan 2017 13:33:58 +1100
changeset 375613 538094a7900cdec31ad57faf9a6937104edce590
parent 374155 8fe52da5cb90209689b41879843ba93f6acda884
child 378343 7311c06a7271a8f33f4f73aa0c88e8d4806e488b
permissions -rw-r--r--
Bug 1332172 - Remove XPC_MAP_WANT_*. r=mccr8. nsIXPCScriptable flags handling in xpc_map_end.h is a bit of a mess. - Half the flags relate to whether various functions are defined (PreCreate, GetProperty, etc). These are set using the XPC_MAP_WANT_* macros; for each one xpc_map_end.h inserts the corresponding flag using the preprocessor (see XPC_MAP_CLASSNAME::GetScriptableFlags()). - The other half of the flags relate to other things (IS_GLOBAL_OBJECT, DONT_REFLECT_INTERFACE_NAMES, etc). These are set using the XPC_MAP_FLAGS macro. Having two similar but different mechanisms to set the flags for a class is confusing. (Indeed, until recently we had some classes where a single flag was redundantly specified via both mechanisms.) Note also that the classes done in dom/base/nsIDOMClassInfo.h also specify all the flags in a single value, similar to how XPC_MAP_FLAGS works. This patch removes the XPC_MAP_WANT_* macros. All flags are now set via XPC_MAP_FLAGS. This is a significant simplification to xpc_map_end.h and all the places that use it. The downside of this change is that I had to change the flag constants from class constants (i.e. nsIXPCScriptable::FOO) to macros (i.e. NSIXPCSCRIPTABLE_FOO) because they need to be used in #if statements like this in xpc_map_end.h: #if !((XPC_MAP_FLAGS) & NSIXPCSCRIPTABLE_WANT_PRECREATE) and you can't use a '::'-qualified name inside a #if. I think this downside is outweighed by the simplification described above. Overall the patch removes 80 lines of code.

/* -*- 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/. */

/*
 * The Components.Sandbox object.
 */

#include "AccessCheck.h"
#include "jsfriendapi.h"
#include "js/Proxy.h"
#include "js/StructuredClone.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsIScriptContext.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "nsNullPrincipal.h"
#include "nsPrincipal.h"
#include "WrapperFactory.h"
#include "xpcprivate.h"
#include "xpc_make_class.h"
#include "XPCWrapper.h"
#include "XrayWrapper.h"
#include "Crypto.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/CSSBinding.h"
#include "mozilla/dom/DirectoryBinding.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#ifdef MOZ_WEBRTC
#include "mozilla/dom/RTCIdentityProviderRegistrar.h"
#endif
#include "mozilla/dom/FileReaderBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TextDecoderBinding.h"
#include "mozilla/dom/TextEncoderBinding.h"
#include "mozilla/dom/UnionConversions.h"
#include "mozilla/dom/URLBinding.h"
#include "mozilla/dom/URLSearchParamsBinding.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "mozilla/DeferredFinalize.h"

using namespace mozilla;
using namespace JS;
using namespace xpc;

using mozilla::dom::DestroyProtoAndIfaceCache;
using mozilla::dom::IndexedDatabaseManager;

NS_IMPL_CYCLE_COLLECTION_CLASS(SandboxPrivate)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
  tmp->UnlinkHostObjectURIs();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate)
  tmp->TraverseHostObjectURIs(cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SandboxPrivate)

NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal)
  NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID;

class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox,
                                      public nsIXPCScriptable
{
public:
    // Aren't macros nice?
    NS_DECL_ISUPPORTS
    NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX
    NS_DECL_NSIXPCSCRIPTABLE

public:
    nsXPCComponents_utils_Sandbox();

private:
    virtual ~nsXPCComponents_utils_Sandbox();

    static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
                                    JSContext* cx, HandleObject obj,
                                    const CallArgs& args, bool* _retval);
};

already_AddRefed<nsIXPCComponents_utils_Sandbox>
xpc::NewSandboxConstructor()
{
    nsCOMPtr<nsIXPCComponents_utils_Sandbox> sbConstructor =
        new nsXPCComponents_utils_Sandbox();
    return sbConstructor.forget();
}

static bool
SandboxDump(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (args.length() == 0)
        return true;

    RootedString str(cx, ToString(cx, args[0]));
    if (!str)
        return false;

    JSAutoByteString utf8str;
    char* cstr = utf8str.encodeUtf8(cx, str);
    if (!cstr)
        return false;

#if defined(XP_MACOSX)
    // Be nice and convert all \r to \n.
    char* c = cstr;
    char* cEnd = cstr + strlen(cstr);
    while (c < cEnd) {
        if (*c == '\r')
            *c = '\n';
        c++;
    }
#endif
#ifdef ANDROID
    __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
#endif

    fputs(cstr, stdout);
    fflush(stdout);
    args.rval().setBoolean(true);
    return true;
}

static bool
SandboxDebug(JSContext* cx, unsigned argc, Value* vp)
{
#ifdef DEBUG
    return SandboxDump(cx, argc, vp);
#else
    return true;
#endif
}

static bool
SandboxImport(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (args.length() < 1 || args[0].isPrimitive()) {
        XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
        return false;
    }

    RootedString funname(cx);
    if (args.length() > 1) {
        // Use the second parameter as the function name.
        funname = ToString(cx, args[1]);
        if (!funname)
            return false;
    } else {
        // NB: funobj must only be used to get the JSFunction out.
        RootedObject funobj(cx, &args[0].toObject());
        if (js::IsProxy(funobj)) {
            funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj);
        }

        JSAutoCompartment ac(cx, funobj);

        RootedValue funval(cx, ObjectValue(*funobj));
        JSFunction* fun = JS_ValueToFunction(cx, funval);
        if (!fun) {
            XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
            return false;
        }

        // Use the actual function name as the name.
        funname = JS_GetFunctionId(fun);
        if (!funname) {
            XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
            return false;
        }
    }

    RootedId id(cx);
    if (!JS_StringToId(cx, funname, &id))
        return false;

    // We need to resolve the this object, because this function is used
    // unbound and should still work and act on the original sandbox.
    RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp));
    if (!thisObject) {
        XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
        return false;
    }
    if (!JS_SetPropertyById(cx, thisObject, id, args[0]))
        return false;

    args.rval().setUndefined();
    return true;
}

static bool
SandboxCreateCrypto(JSContext* cx, JS::HandleObject obj)
{
    MOZ_ASSERT(JS_IsGlobalObject(obj));

    nsIGlobalObject* native = xpc::NativeGlobal(obj);
    MOZ_ASSERT(native);

    dom::Crypto* crypto = new dom::Crypto();
    crypto->Init(native);
    JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr));
    return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE);
}

#ifdef MOZ_WEBRTC
static bool
SandboxCreateRTCIdentityProvider(JSContext* cx, JS::HandleObject obj)
{
    MOZ_ASSERT(JS_IsGlobalObject(obj));

    nsCOMPtr<nsIGlobalObject> nativeGlobal = xpc::NativeGlobal(obj);
    MOZ_ASSERT(nativeGlobal);

    dom::RTCIdentityProviderRegistrar* registrar =
            new dom::RTCIdentityProviderRegistrar(nativeGlobal);
    JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr));
    return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped, JSPROP_ENUMERATE);
}
#endif

static bool
SetFetchRequestFromValue(JSContext *cx, RequestOrUSVString& request,
                         const MutableHandleValue& requestOrUrl)
{
    RequestOrUSVStringArgument requestHolder(request);
    bool noMatch = true;
    if (requestOrUrl.isObject() &&
        !requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) {
        return false;
    }
    if (noMatch &&
        !requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) {
        return false;
    }
    if (noMatch) {
        return false;
    }
    return true;
}

static bool
SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args)
{
    if (args.length() < 1) {
        JS_ReportErrorASCII(cx, "fetch requires at least 1 argument");
        return false;
    }

    RequestOrUSVString request;
    if (!SetFetchRequestFromValue(cx, request, args[0])) {
        JS_ReportErrorASCII(cx, "fetch requires a string or Request in argument 1");
        return false;
    }
    RootedDictionary<dom::RequestInit> options(cx);
    if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
                      "Argument 2 of fetch", false)) {
        return false;
    }
    nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
    if (!global) {
        return false;
    }
    ErrorResult rv;
    RefPtr<dom::Promise> response =
        FetchRequest(global, Constify(request), Constify(options), rv);
    if (rv.MaybeSetPendingException(cx)) {
        return false;
    }

    args.rval().setObject(*response->PromiseObj());
    return true;
}

static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedObject callee(cx, &args.callee());
    RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
    if (SandboxFetch(cx, scope, args)) {
        return true;
    }
    return ConvertExceptionToPromise(cx, scope, args.rval());
}


static bool
SandboxCreateFetch(JSContext* cx, HandleObject obj)
{
    MOZ_ASSERT(JS_IsGlobalObject(obj));

    return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
        dom::RequestBinding::GetConstructorObject(cx) &&
        dom::ResponseBinding::GetConstructorObject(cx) &&
        dom::HeadersBinding::GetConstructorObject(cx);
}

static bool
SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (args.length() < 1) {
        JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
        return false;
    }
    if (!args[0].isObject()) {
        args.rval().setBoolean(false);
        return true;
    }

    RootedObject obj(cx, &args[0].toObject());
    obj = js::CheckedUnwrap(obj);
    NS_ENSURE_TRUE(obj, false);

    args.rval().setBoolean(js::IsScriptedProxy(obj));
    return true;
}

/*
 * Expected type of the arguments and the return value:
 * function exportFunction(function funToExport,
 *                         object targetScope,
 *                         [optional] object options)
 */
static bool
SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (args.length() < 2) {
        JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
        return false;
    }

    RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
    return ExportFunction(cx, args[0], args[1], options, args.rval());
}

static bool
SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (args.length() < 1) {
        JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
        return false;
    }

    RootedObject optionsObj(cx);
    bool calledWithOptions = args.length() > 1;
    if (calledWithOptions) {
        if (!args[1].isObject()) {
            JS_ReportErrorASCII(cx, "Expected the 2nd argument (options) to be an object");
            return false;
        }
        optionsObj = &args[1].toObject();
    }

    CreateObjectInOptions options(cx, optionsObj);
    if (calledWithOptions && !options.Parse())
        return false;

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

static bool
SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    if (args.length() < 2) {
        JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
        return false;
    }

    RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
    return xpc::CloneInto(cx, args[0], args[1], options, args.rval());
}

static void
sandbox_finalize(js::FreeOp* fop, JSObject* obj)
{
    nsIScriptObjectPrincipal* sop =
        static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(obj));
    if (!sop) {
        // sop can be null if CreateSandboxObject fails in the middle.
        return;
    }

    static_cast<SandboxPrivate*>(sop)->ForgetGlobalObject();
    DestroyProtoAndIfaceCache(obj);
    DeferredFinalize(sop);
}

static void
sandbox_moved(JSObject* obj, const JSObject* old)
{
    // Note that this hook can be called before the private pointer is set. In
    // this case the SandboxPrivate will not exist yet, so there is nothing to
    // do.
    nsIScriptObjectPrincipal* sop =
        static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(obj));
    if (sop)
        static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
}

static bool
writeToProto_setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                         JS::MutableHandleValue vp, JS::ObjectOpResult& result)
{
    RootedObject proto(cx);
    if (!JS_GetPrototype(cx, obj, &proto))
        return false;

    RootedValue receiver(cx, ObjectValue(*proto));
    return JS_ForwardSetPropertyTo(cx, proto, id, vp, receiver, result);
}

static bool
writeToProto_getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                    JS::MutableHandleValue vp)
{
    RootedObject proto(cx);
    if (!JS_GetPrototype(cx, obj, &proto))
        return false;

    return JS_GetPropertyById(cx, proto, id, vp);
}

struct AutoSkipPropertyMirroring
{
    explicit AutoSkipPropertyMirroring(CompartmentPrivate* priv) : priv(priv) {
        MOZ_ASSERT(!priv->skipWriteToGlobalPrototype);
        priv->skipWriteToGlobalPrototype = true;
    }
    ~AutoSkipPropertyMirroring() {
        MOZ_ASSERT(priv->skipWriteToGlobalPrototype);
        priv->skipWriteToGlobalPrototype = false;
    }

  private:
    CompartmentPrivate* priv;
};

// This hook handles the case when writeToGlobalPrototype is set on the
// sandbox. This flag asks that any properties defined on the sandbox global
// also be defined on the sandbox global's prototype. Whenever one of these
// properties is changed (on either side), the change should be reflected on
// both sides. We use this functionality to create sandboxes that are
// essentially "sub-globals" of another global. This is useful for running
// add-ons in a separate compartment while still giving them access to the
// chrome window.
static bool
sandbox_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
{
    CompartmentPrivate* priv = CompartmentPrivate::Get(obj);
    MOZ_ASSERT(priv->writeToGlobalPrototype);

    // Whenever JS_EnumerateStandardClasses is called, it defines the
    // "undefined" property, even if it's already defined. We don't want to do
    // anything in that case.
    if (id == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_UNDEFINED))
        return true;

    // Avoid recursively triggering sandbox_addProperty in the
    // JS_DefinePropertyById call below.
    if (priv->skipWriteToGlobalPrototype)
        return true;

    AutoSkipPropertyMirroring askip(priv);

    RootedObject proto(cx);
    if (!JS_GetPrototype(cx, obj, &proto))
        return false;

    // After bug 1015790 is fixed, we should be able to remove this unwrapping.
    RootedObject unwrappedProto(cx, js::UncheckedUnwrap(proto, /* stopAtWindowProxy = */ false));

    Rooted<JS::PropertyDescriptor> pd(cx);
    if (!JS_GetPropertyDescriptorById(cx, proto, id, &pd))
        return false;

    // This is a little icky. If the property exists and is not configurable,
    // then JS_CopyPropertyFrom will throw an exception when we try to do a
    // normal assignment since it will think we're trying to remove the
    // non-configurability. So we do JS_SetPropertyById in that case.
    //
    // However, in the case of |const x = 3|, we get called once for
    // JSOP_DEFCONST and once for JSOP_SETCONST. The first one creates the
    // property as readonly and configurable. The second one changes the
    // attributes to readonly and not configurable. If we use JS_SetPropertyById
    // for the second call, it will throw an exception because the property is
    // readonly. We have to use JS_CopyPropertyFrom since it ignores the
    // readonly attribute (as it calls JSObject::defineProperty). See bug
    // 1019181.
    if (pd.object() && !pd.configurable()) {
        if (!JS_SetPropertyById(cx, proto, id, v))
            return false;
    } else {
        if (!JS_CopyPropertyFrom(cx, id, unwrappedProto, obj,
                                 MakeNonConfigurableIntoConfigurable))
            return false;
    }

    if (!JS_GetPropertyDescriptorById(cx, obj, id, &pd))
        return false;
    unsigned attrs = pd.attributes() & ~(JSPROP_GETTER | JSPROP_SETTER);
    if (!JS_DefinePropertyById(cx, obj, id, v,
                               attrs | JSPROP_PROPOP_ACCESSORS | JSPROP_REDEFINE_NONCONFIGURABLE,
                               JS_PROPERTYOP_GETTER(writeToProto_getProperty),
                               JS_PROPERTYOP_SETTER(writeToProto_setProperty)))
        return false;

    return true;
}

#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)

static const js::ClassOps SandboxClassOps = {
    nullptr, nullptr, nullptr, nullptr,
    JS_EnumerateStandardClasses, JS_ResolveStandardClass,
    JS_MayResolveStandardClass,
    sandbox_finalize,
    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
};

static const js::ClassExtension SandboxClassExtension = {
    nullptr,      /* weakmapKeyDelegateOp */
    sandbox_moved /* objectMovedOp */
};

static const js::Class SandboxClass = {
    "Sandbox",
    XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) |
    JSCLASS_FOREGROUND_FINALIZE,
    &SandboxClassOps,
    JS_NULL_CLASS_SPEC,
    &SandboxClassExtension,
    JS_NULL_OBJECT_OPS
};

// Note to whomever comes here to remove addProperty hooks: billm has promised
// to do the work for this class.
static const js::ClassOps SandboxWriteToProtoClassOps = {
    sandbox_addProperty, nullptr, nullptr, nullptr,
    JS_EnumerateStandardClasses, JS_ResolveStandardClass,
    JS_MayResolveStandardClass,
    sandbox_finalize,
    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
};

static const js::Class SandboxWriteToProtoClass = {
    "Sandbox",
    XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) |
    JSCLASS_FOREGROUND_FINALIZE,
    &SandboxWriteToProtoClassOps,
    JS_NULL_CLASS_SPEC,
    &SandboxClassExtension,
    JS_NULL_OBJECT_OPS
};

static const JSFunctionSpec SandboxFunctions[] = {
    JS_FS("dump",    SandboxDump,    1,0),
    JS_FS("debug",   SandboxDebug,   1,0),
    JS_FS("importFunction", SandboxImport, 1,0),
    JS_FS_END
};

bool
xpc::IsSandbox(JSObject* obj)
{
    const js::Class* clasp = js::GetObjectClass(obj);
    return clasp == &SandboxClass || clasp == &SandboxWriteToProtoClass;
}

/***************************************************************************/
nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox()
{
}

nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox()
{
}

NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox)
  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox)
  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox)
NS_INTERFACE_MAP_END

NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox)
NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox)

// We use the nsIXPScriptable macros to generate lots of stuff for us.
#define XPC_MAP_CLASSNAME         nsXPCComponents_utils_Sandbox
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox"
#define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
                       XPC_SCRIPTABLE_WANT_CONSTRUCT)
#include "xpc_map_end.h" /* This #undef's the above. */

const xpc::SandboxProxyHandler xpc::sandboxProxyHandler;

bool
xpc::IsSandboxPrototypeProxy(JSObject* obj)
{
    return js::IsProxy(obj) &&
           js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler;
}

bool
xpc::SandboxCallableProxyHandler::call(JSContext* cx, JS::Handle<JSObject*> proxy,
                                       const JS::CallArgs& args) const
{
    // We forward the call to our underlying callable.

    // Get our SandboxProxyHandler proxy.
    RootedObject sandboxProxy(cx, getSandboxProxy(proxy));
    MOZ_ASSERT(js::IsProxy(sandboxProxy) &&
               js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler);

    // The global of the sandboxProxy is the sandbox global, and the
    // target object is the original proto.
    RootedObject sandboxGlobal(cx,
      js::GetGlobalForObjectCrossCompartment(sandboxProxy));
    MOZ_ASSERT(IsSandbox(sandboxGlobal));

    // If our this object is the sandbox global, we call with this set to the
    // original proto instead.
    //
    // There are two different ways we can compute |this|. If we use
    // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the
    // caller, which may be undefined if a global function was invoked without
    // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this|
    // in |vp| will be coerced to the global, which is not the correct
    // behavior in ES5 strict mode. And we have no way to compute strictness
    // here.
    //
    // The naive approach is simply to use JS_THIS_VALUE here. If |this| was
    // explicit, we can remap it appropriately. If it was implicit, then we
    // leave it as undefined, and let the callee sort it out. Since the callee
    // is generally in the same compartment as its global (eg the Window's
    // compartment, not the Sandbox's), the callee will generally compute the
    // correct |this|.
    //
    // However, this breaks down in the Xray case. If the sandboxPrototype
    // is an Xray wrapper, then we'll end up reifying the native methods in
    // the Sandbox's scope, which means that they'll compute |this| to be the
    // Sandbox, breaking old-style XPC_WN_CallMethod methods.
    //
    // Luckily, the intent of Xrays is to provide a vanilla view of a foreign
    // DOM interface, which means that we don't care about script-enacted
    // strictness in the prototype's home compartment. Indeed, since DOM
    // methods are always non-strict, we can just assume non-strict semantics
    // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately
    // remap |this|.
    bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy);
    RootedValue thisVal(cx, isXray ? args.computeThis(cx) : args.thisv());
    if (thisVal == ObjectValue(*sandboxGlobal)) {
        thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
    }

    RootedValue func(cx, js::GetProxyPrivate(proxy));
    return JS::Call(cx, thisVal, func, args, args.rval());
}

const xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler;

/*
 * Wrap a callable such that if we're called with oldThisObj as the
 * "this" we will instead call it with newThisObj as the this.
 */
static JSObject*
WrapCallable(JSContext* cx, HandleObject callable, HandleObject sandboxProtoProxy)
{
    MOZ_ASSERT(JS::IsCallable(callable));
    // Our proxy is wrapping the callable.  So we need to use the
    // callable as the private.  We put the given sandboxProtoProxy in
    // an extra slot, and our call() hook depends on that.
    MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) &&
               js::GetProxyHandler(sandboxProtoProxy) ==
                 &xpc::sandboxProxyHandler);

    RootedValue priv(cx, ObjectValue(*callable));
    // We want to claim to have the same proto as our wrapped callable, so set
    // ourselves up with a lazy proto.
    js::ProxyOptions options;
    options.setLazyProto(true);
    JSObject* obj = js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler,
                                       priv, nullptr, options);
    if (obj) {
        js::SetProxyExtra(obj, SandboxCallableProxyHandler::SandboxProxySlot,
                          ObjectValue(*sandboxProtoProxy));
    }

    return obj;
}

template<typename Op>
bool WrapAccessorFunction(JSContext* cx, Op& op, PropertyDescriptor* desc,
                          unsigned attrFlag, HandleObject sandboxProtoProxy)
{
    if (!op) {
        return true;
    }

    if (!(desc->attrs & attrFlag)) {
        XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
        return false;
    }

    RootedObject func(cx, JS_FUNC_TO_DATA_PTR(JSObject*, op));
    func = WrapCallable(cx, func, sandboxProtoProxy);
    if (!func)
        return false;
    op = JS_DATA_TO_FUNC_PTR(Op, func.get());
    return true;
}

bool
xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext* cx,
                                                JS::Handle<JSObject*> proxy,
                                                JS::Handle<jsid> id,
                                                JS::MutableHandle<PropertyDescriptor> desc) const
{
    JS::RootedObject obj(cx, wrappedObject(proxy));

    MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy));
    if (!JS_GetPropertyDescriptorById(cx, obj, id, desc))
        return false;

    if (!desc.object())
        return true; // No property, nothing to do

    // Now fix up the getter/setter/value as needed to be bound to desc->obj.
    if (!WrapAccessorFunction(cx, desc.getter(), desc.address(),
                              JSPROP_GETTER, proxy))
        return false;
    if (!WrapAccessorFunction(cx, desc.setter(), desc.address(),
                              JSPROP_SETTER, proxy))
        return false;
    if (desc.value().isObject()) {
        RootedObject val (cx, &desc.value().toObject());
        if (JS::IsCallable(val)) {
            val = WrapCallable(cx, val, proxy);
            if (!val)
                return false;
            desc.value().setObject(*val);
        }
    }

    return true;
}

bool
xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext* cx,
                                                   JS::Handle<JSObject*> proxy,
                                                   JS::Handle<jsid> id,
                                                   JS::MutableHandle<PropertyDescriptor> desc)
                                                   const
{
    if (!getPropertyDescriptor(cx, proxy, id, desc))
        return false;

    if (desc.object() != wrappedObject(proxy))
        desc.object().set(nullptr);

    return true;
}

/*
 * Reuse the BaseProxyHandler versions of the derived traps that are implemented
 * in terms of the fundamental traps.
 */

bool
xpc::SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<jsid> id, bool* bp) const
{
    // This uses getPropertyDescriptor for backward compatibility with
    // the old BaseProxyHandler::has implementation.
    Rooted<PropertyDescriptor> desc(cx);
    if (!getPropertyDescriptor(cx, proxy, id, &desc))
        return false;

    *bp = !!desc.object();
    return true;
}
bool
xpc::SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                                 JS::Handle<jsid> id, bool* bp) const
{
    return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
}

bool
xpc::SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<JS::Value> receiver,
                              JS::Handle<jsid> id,
                              JS::MutableHandle<Value> vp) const
{
    // This uses getPropertyDescriptor for backward compatibility with
    // the old BaseProxyHandler::get implementation.
    Rooted<PropertyDescriptor> desc(cx);
    if (!getPropertyDescriptor(cx, proxy, id, &desc))
        return false;
    desc.assertCompleteIfFound();

    if (!desc.object()) {
        vp.setUndefined();
        return true;
    }

    // Everything after here follows [[Get]] for ordinary objects.
    if (desc.isDataDescriptor()) {
        vp.set(desc.value());
        return true;
    }

    MOZ_ASSERT(desc.isAccessorDescriptor());
    RootedObject getter(cx, desc.getterObject());

    if (!getter) {
        vp.setUndefined();
        return true;
    }

    return Call(cx, receiver, getter, HandleValueArray::empty(), vp);
}

bool
xpc::SandboxProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<jsid> id,
                              JS::Handle<Value> v,
                              JS::Handle<Value> receiver,
                              JS::ObjectOpResult& result) const
{
    return BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
}

bool
xpc::SandboxProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx,
                                                       JS::Handle<JSObject*> proxy,
                                                       AutoIdVector& props) const
{
    return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
}

bool
xpc::SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
                                    JS::MutableHandle<JSObject*> objp) const
{
    return BaseProxyHandler::enumerate(cx, proxy, objp);
}

bool
xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj)
{
    uint32_t length;
    bool ok = JS_GetArrayLength(cx, obj, &length);
    NS_ENSURE_TRUE(ok, false);
    for (uint32_t i = 0; i < length; i++) {
        RootedValue nameValue(cx);
        ok = JS_GetElement(cx, obj, i, &nameValue);
        NS_ENSURE_TRUE(ok, false);
        if (!nameValue.isString()) {
            JS_ReportErrorASCII(cx, "Property names must be strings");
            return false;
        }
        RootedString nameStr(cx, nameValue.toString());
        JSAutoByteString name;
        if (!name.encodeUtf8(cx, nameStr))
            return false;
        if (!strcmp(name.ptr(), "CSS")) {
            CSS = true;
        } else if (!strcmp(name.ptr(), "indexedDB")) {
            indexedDB = true;
        } else if (!strcmp(name.ptr(), "XMLHttpRequest")) {
            XMLHttpRequest = true;
        } else if (!strcmp(name.ptr(), "TextEncoder")) {
            TextEncoder = true;
        } else if (!strcmp(name.ptr(), "TextDecoder")) {
            TextDecoder = true;
        } else if (!strcmp(name.ptr(), "URL")) {
            URL = true;
        } else if (!strcmp(name.ptr(), "URLSearchParams")) {
            URLSearchParams = true;
        } else if (!strcmp(name.ptr(), "atob")) {
            atob = true;
        } else if (!strcmp(name.ptr(), "btoa")) {
            btoa = true;
        } else if (!strcmp(name.ptr(), "Blob")) {
            Blob = true;
        } else if (!strcmp(name.ptr(), "Directory")) {
            Directory = true;
        } else if (!strcmp(name.ptr(), "File")) {
            File = true;
        } else if (!strcmp(name.ptr(), "crypto")) {
            crypto = true;
#ifdef MOZ_WEBRTC
        } else if (!strcmp(name.ptr(), "rtcIdentityProvider")) {
            rtcIdentityProvider = true;
#endif
        } else if (!strcmp(name.ptr(), "fetch")) {
            fetch = true;
        } else if (!strcmp(name.ptr(), "caches")) {
            caches = true;
        } else if (!strcmp(name.ptr(), "FileReader")) {
            fileReader = true;
        } else {
            JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.ptr());
            return false;
        }
    }
    return true;
}

bool
xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
{
    MOZ_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(obj));
    // Properties will be exposed to System automatically but not to Sandboxes
    // if |[Exposed=System]| is specified.
    // This function holds common properties not exposed automatically but able
    // to be requested either in |Cu.importGlobalProperties| or
    // |wantGlobalProperties| of a sandbox.
    if (CSS && !dom::CSSBinding::GetConstructorObject(cx))
        return false;

    if (XMLHttpRequest &&
        !dom::XMLHttpRequestBinding::GetConstructorObject(cx))
        return false;

    if (TextEncoder &&
        !dom::TextEncoderBinding::GetConstructorObject(cx))
        return false;

    if (TextDecoder &&
        !dom::TextDecoderBinding::GetConstructorObject(cx))
        return false;

    if (URL &&
        !dom::URLBinding::GetConstructorObject(cx))
        return false;

    if (URLSearchParams &&
        !dom::URLSearchParamsBinding::GetConstructorObject(cx))
        return false;

    if (atob &&
        !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0))
        return false;

    if (btoa &&
        !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0))
        return false;

    if (Blob &&
        !dom::BlobBinding::GetConstructorObject(cx))
        return false;

    if (Directory &&
        !dom::DirectoryBinding::GetConstructorObject(cx))
        return false;

    if (File &&
        !dom::FileBinding::GetConstructorObject(cx))
        return false;

    if (crypto && !SandboxCreateCrypto(cx, obj))
        return false;

#ifdef MOZ_WEBRTC
    if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj))
        return false;
#endif

    if (fetch && !SandboxCreateFetch(cx, obj))
        return false;

    if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj))
        return false;

    if (fileReader && !dom::FileReaderBinding::GetConstructorObject(cx))
        return false;

    return true;
}

bool
xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx, JS::HandleObject obj)
{
    if (indexedDB &&
        !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
        return false;

    return Define(cx, obj);
}

bool
xpc::GlobalProperties::DefineInSandbox(JSContext* cx, JS::HandleObject obj)
{
    MOZ_ASSERT(IsSandbox(obj));
    MOZ_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(obj));

    if (indexedDB &&
        !(IndexedDatabaseManager::ResolveSandboxBinding(cx) &&
          IndexedDatabaseManager::DefineIndexedDB(cx, obj)))
        return false;

    return Define(cx, obj);
}

nsresult
xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prinOrSop,
                         SandboxOptions& options)
{
    // Create the sandbox global object
    nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
    if (!principal) {
        nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop);
        if (sop) {
            principal = sop->GetPrincipal();
        } else {
            RefPtr<nsNullPrincipal> nullPrin = nsNullPrincipal::Create();
            principal = nullPrin;
        }
    }
    MOZ_ASSERT(principal);

    JS::CompartmentOptions compartmentOptions;

    auto& creationOptions = compartmentOptions.creationOptions();

    // XXXjwatt: Consider whether/when sandboxes should be able to see
    // [SecureContext] API (bug 1273687).  In that case we'd call
    // creationOptions.setSecureContext(true).

    if (xpc::SharedMemoryEnabled())
        creationOptions.setSharedMemoryAndAtomicsEnabled(true);

    if (options.sameZoneAs)
        creationOptions.setSameZoneAs(js::UncheckedUnwrap(options.sameZoneAs));
    else if (options.freshZone)
        creationOptions.setZone(JS::FreshZone);
    else
        creationOptions.setZone(JS::SystemZone);

    creationOptions.setInvisibleToDebugger(options.invisibleToDebugger)
                   .setTrace(TraceXPCGlobal);

    // Try to figure out any addon this sandbox should be associated with.
    // The addon could have been passed in directly, as part of the metadata,
    // or by being constructed from an addon's code.
    JSAddonId* addonId = nullptr;
    if (options.addonId) {
        addonId = JS::NewAddonId(cx, options.addonId);
        NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE);
    } else if (JSObject* obj = JS::CurrentGlobalOrNull(cx)) {
        if (JSAddonId* id = JS::AddonIdOfObject(obj))
            addonId = id;
    }

    creationOptions.setAddonId(addonId);

    compartmentOptions.behaviors().setDiscardSource(options.discardSource);

    const js::Class* clasp = options.writeToGlobalPrototype
                             ? &SandboxWriteToProtoClass
                             : &SandboxClass;

    RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, js::Jsvalify(clasp),
                                                     principal, compartmentOptions));
    if (!sandbox)
        return NS_ERROR_FAILURE;

    CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
    priv->allowWaivers = options.allowWaivers;
    priv->writeToGlobalPrototype = options.writeToGlobalPrototype;
    priv->isWebExtensionContentScript = options.isWebExtensionContentScript;
    priv->waiveInterposition = options.waiveInterposition;

    // Set up the wantXrays flag, which indicates whether xrays are desired even
    // for same-origin access.
    //
    // This flag has historically been ignored for chrome sandboxes due to
    // quirks in the wrapping implementation that have now been removed. Indeed,
    // same-origin Xrays for chrome->chrome access seems a bit superfluous.
    // Arguably we should just flip the default for chrome and still honor the
    // flag, but such a change would break code in subtle ways for minimal
    // benefit. So we just switch it off here.
    priv->wantXrays =
      AccessCheck::isChrome(sandbox) ? false : options.wantXrays;

    {
        JSAutoCompartment ac(cx, sandbox);

        nsCOMPtr<nsIScriptObjectPrincipal> sbp =
            new SandboxPrivate(principal, sandbox);

        // Pass on ownership of sbp to |sandbox|.
        JS_SetPrivate(sandbox, sbp.forget().take());

        {
            // Don't try to mirror standard class properties, if we're using a
            // mirroring sandbox.  (This is meaningless for non-mirroring
            // sandboxes.)
            AutoSkipPropertyMirroring askip(CompartmentPrivate::Get(sandbox));

            // Ensure |Object.prototype| is instantiated before prototype-
            // splicing below.  For write-to-global-prototype behavior, extend
            // this to all builtin properties.
            if (options.writeToGlobalPrototype) {
                if (!JS_EnumerateStandardClasses(cx, sandbox))
                    return NS_ERROR_XPC_UNEXPECTED;
            } else {
                if (!JS_GetObjectPrototype(cx, sandbox))
                    return NS_ERROR_XPC_UNEXPECTED;
            }
        }

        if (options.proto) {
            bool ok = JS_WrapObject(cx, &options.proto);
            if (!ok)
                return NS_ERROR_XPC_UNEXPECTED;

            // Now check what sort of thing we've got in |proto|, and figure out
            // if we need a SandboxProxyHandler.
            //
            // Note that, in the case of a window, we can't require that the
            // Sandbox subsumes the prototype, because we have to hold our
            // reference to it via an outer window, and the window may navigate
            // at any time. So we have to handle that case separately.
            bool useSandboxProxy = !!WindowOrNull(js::UncheckedUnwrap(options.proto, false));
            if (!useSandboxProxy) {
                JSObject* unwrappedProto = js::CheckedUnwrap(options.proto, false);
                if (!unwrappedProto) {
                    JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype");
                    return NS_ERROR_INVALID_ARG;
                }
                const js::Class* unwrappedClass = js::GetObjectClass(unwrappedProto);
                useSandboxProxy = IS_WN_CLASS(unwrappedClass) ||
                                  mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass));
            }

            if (useSandboxProxy) {
                // Wrap it up in a proxy that will do the right thing in terms
                // of this-binding for methods.
                RootedValue priv(cx, ObjectValue(*options.proto));
                options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler,
                                                   priv, nullptr);
                if (!options.proto)
                    return NS_ERROR_OUT_OF_MEMORY;
            }

            ok = JS_SplicePrototype(cx, sandbox, options.proto);
            if (!ok)
                return NS_ERROR_XPC_UNEXPECTED;
        }

        // Don't try to mirror the properties that are set below.
        AutoSkipPropertyMirroring askip(CompartmentPrivate::Get(sandbox));

        bool allowComponents = principal == nsXPConnect::SystemPrincipal() ||
                               nsContentUtils::IsExpandedPrincipal(principal);
        if (options.wantComponents && allowComponents &&
            !ObjectScope(sandbox)->AttachComponentsObject(cx))
            return NS_ERROR_XPC_UNEXPECTED;

        if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox))
            return NS_ERROR_XPC_UNEXPECTED;

        if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions))
            return NS_ERROR_XPC_UNEXPECTED;

        if (options.wantExportHelpers &&
            (!JS_DefineFunction(cx, sandbox, "exportFunction", SandboxExportFunction, 3, 0) ||
             !JS_DefineFunction(cx, sandbox, "createObjectIn", SandboxCreateObjectIn, 2, 0) ||
             !JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) ||
             !JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0)))
            return NS_ERROR_XPC_UNEXPECTED;

        if (!options.globalProperties.DefineInSandbox(cx, sandbox))
            return NS_ERROR_XPC_UNEXPECTED;
    }

    // We handle the case where the context isn't in a compartment for the
    // benefit of InitSingletonScopes.
    vp.setObject(*sandbox);
    if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp))
        return NS_ERROR_UNEXPECTED;

    // Set the location information for the new global, so that tools like
    // about:memory may use that information
    xpc::SetLocationForGlobal(sandbox, options.sandboxName);

    xpc::SetSandboxMetadata(cx, sandbox, options.metadata);

    JSAutoCompartment ac(cx, sandbox);
    JS_FireOnNewGlobalObject(cx, sandbox);

    return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
                                    JSObject* objArg, const CallArgs& args, bool* _retval)
{
    RootedObject obj(cx, objArg);
    return CallOrConstruct(wrapper, cx, obj, args, _retval);
}

NS_IMETHODIMP
nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
                                         JSObject* objArg, const CallArgs& args, bool* _retval)
{
    RootedObject obj(cx, objArg);
    return CallOrConstruct(wrapper, cx, obj, args, _retval);
}

/*
 * For sandbox constructor the first argument can be a URI string in which case
 * we use the related Codebase Principal for the sandbox.
 */
bool
ParsePrincipal(JSContext* cx, HandleString codebase, const OriginAttributes& aAttrs,
               nsIPrincipal** principal)
{
    MOZ_ASSERT(principal);
    MOZ_ASSERT(codebase);
    nsCOMPtr<nsIURI> uri;
    nsAutoJSString codebaseStr;
    NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), false);
    nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr);
    if (NS_FAILED(rv)) {
        JS_ReportErrorASCII(cx, "Creating URI from string failed");
        return false;
    }

    // We could allow passing in the app-id and browser-element info to the
    // sandbox constructor. But creating a sandbox based on a string is a
    // deprecated API so no need to add features to it.
    nsCOMPtr<nsIPrincipal> prin =
        BasePrincipal::CreateCodebasePrincipal(uri, aAttrs);
    prin.forget(principal);

    if (!*principal) {
        JS_ReportErrorASCII(cx, "Creating Principal from URI failed");
        return false;
    }
    return true;
}

/*
 * For sandbox constructor the first argument can be a principal object or
 * a script object principal (Document, Window).
 */
static bool
GetPrincipalOrSOP(JSContext* cx, HandleObject from, nsISupports** out)
{
    MOZ_ASSERT(out);
    *out = nullptr;

    nsXPConnect* xpc = nsXPConnect::XPConnect();
    nsISupports* native = xpc->GetNativeOfWrapper(cx, from);

    if (nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(native)) {
        sop.forget(out);
        return true;
    }

    nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(native);
    principal.forget(out);
    NS_ENSURE_TRUE(*out, false);

    return true;
}

/*
 * The first parameter of the sandbox constructor might be an array of principals, either in string
 * format or actual objects (see GetPrincipalOrSOP)
 */
static bool
GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj,
                     const SandboxOptions& options, nsIExpandedPrincipal** out)
{
    MOZ_ASSERT(out);
    uint32_t length;

    if (!JS_GetArrayLength(cx, arrayObj, &length))
        return false;
    if (!length) {
        // We need a whitelist of principals or uri strings to create an
        // expanded principal, if we got an empty array or something else
        // report error.
        JS_ReportErrorASCII(cx, "Expected an array of URI strings");
        return false;
    }

    nsTArray< nsCOMPtr<nsIPrincipal> > allowedDomains(length);
    allowedDomains.SetLength(length);

    // If an originAttributes option has been specified, we will use that as the
    // OriginAttribute of all of the string arguments passed to this function.
    // Otherwise, we will use the OriginAttributes of a principal or SOP object
    // in the array, if any.  If no such object is present, and all we have are
    // strings, then we will use a default OriginAttribute.
    // Otherwise, we will use the origin attributes of the passed object(s). If
    // more than one object is specified, we ensure that the OAs match.
    Maybe<OriginAttributes> attrs;
    if (options.originAttributes) {
        attrs.emplace();
        JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes));
        if (!attrs->Init(cx, val)) {
            // The originAttributes option, if specified, must be valid!
            JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object");
            return false;
        }
    }

    // Now we go over the array in two passes.  In the first pass, we ignore
    // strings, and only process objects.  Assuming that no originAttributes
    // option has been passed, if we encounter a principal or SOP object, we
    // grab its OA and save it if it's the first OA encountered, otherwise
    // check to make sure that it is the same as the OA found before.
    // In the second pass, we ignore objects, and use the OA found in pass 0
    // (or the previously computed OA if we have obtained it from the options)
    // to construct codebase principals.
    //
    // The effective OA selected above will also be set as the OA of the
    // expanded principal object.

    // First pass:
    for (uint32_t i = 0; i < length; ++i) {
        RootedValue allowed(cx);
        if (!JS_GetElement(cx, arrayObj, i, &allowed))
            return false;

        nsresult rv;
        nsCOMPtr<nsIPrincipal> principal;
        if (allowed.isObject()) {
            // In case of object let's see if it's a Principal or a ScriptObjectPrincipal.
            nsCOMPtr<nsISupports> prinOrSop;
            RootedObject obj(cx, &allowed.toObject());
            if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)))
                return false;

            nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(prinOrSop));
            principal = do_QueryInterface(prinOrSop);
            if (sop)
                principal = sop->GetPrincipal();
            NS_ENSURE_TRUE(principal, false);

            if (!options.originAttributes) {
                const OriginAttributes prinAttrs =
                    principal->OriginAttributesRef();
                if (attrs.isNothing()) {
                    attrs.emplace(prinAttrs);
                } else if (prinAttrs != attrs.ref()) {
                    // If attrs is from a previously encountered principal in the
                    // array, we need to ensure that it matches the OA of the
                    // principal we have here.
                    // If attrs comes from OriginAttributes, we don't need
                    // this check.
                    return false;
                }
            }

            // We do not allow ExpandedPrincipals to contain any system principals.
            bool isSystem;
            rv = nsXPConnect::SecurityManager()->IsSystemPrincipal(principal, &isSystem);
            NS_ENSURE_SUCCESS(rv, false);
            if (isSystem) {
                JS_ReportErrorASCII(cx, "System principal is not allowed in an expanded principal");
                return false;
            }
            allowedDomains[i] = principal;
        } else if (allowed.isString()) {
            // Skip any string arguments - we handle them in the next pass.
        } else {
            // Don't know what this is.
            return false;
        }
    }

    if (attrs.isNothing()) {
        // If no OriginAttributes was found in the first pass, fall back to a default one.
        attrs.emplace();
    }

    // Second pass:
    for (uint32_t i = 0; i < length; ++i) {
        RootedValue allowed(cx);
        if (!JS_GetElement(cx, arrayObj, i, &allowed))
            return false;

        nsCOMPtr<nsIPrincipal> principal;
        if (allowed.isString()) {
            // In case of string let's try to fetch a codebase principal from it.
            RootedString str(cx, allowed.toString());

            // attrs here is either a default OriginAttributes in case the
            // originAttributes option isn't specified, and no object in the array
            // provides a principal.  Otherwise it's either the forced principal, or
            // the principal found before, so we can use it here.
            if (!ParsePrincipal(cx, str, attrs.ref(), getter_AddRefs(principal)))
                return false;
            NS_ENSURE_TRUE(principal, false);
            allowedDomains[i] = principal;
        } else {
            MOZ_ASSERT(allowed.isObject());
        }
    }

    nsCOMPtr<nsIExpandedPrincipal> result =
        new nsExpandedPrincipal(allowedDomains, attrs.ref());
    result.forget(out);
    return true;
}

/*
 * Helper that tries to get a property from the options object.
 */
bool
OptionsBase::ParseValue(const char* name, MutableHandleValue prop, bool* aFound)
{
    bool found;
    bool ok = JS_HasProperty(mCx, mObject, name, &found);
    NS_ENSURE_TRUE(ok, false);

    if (aFound)
        *aFound = found;

    if (!found)
        return true;

    return JS_GetProperty(mCx, mObject, name, prop);
}

/*
 * Helper that tries to get a boolean property from the options object.
 */
bool
OptionsBase::ParseBoolean(const char* name, bool* prop)
{
    MOZ_ASSERT(prop);
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if (!value.isBoolean()) {
        JS_ReportErrorASCII(mCx, "Expected a boolean value for property %s", name);
        return false;
    }

    *prop = value.toBoolean();
    return true;
}

/*
 * Helper that tries to get an object property from the options object.
 */
bool
OptionsBase::ParseObject(const char* name, MutableHandleObject prop)
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if (!value.isObject()) {
        JS_ReportErrorASCII(mCx, "Expected an object value for property %s", name);
        return false;
    }
    prop.set(&value.toObject());
    return true;
}

/*
 * Helper that tries to get an object property from the options object.
 */
bool
OptionsBase::ParseJSString(const char* name, MutableHandleString prop)
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if (!value.isString()) {
        JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
        return false;
    }
    prop.set(value.toString());
    return true;
}

/*
 * Helper that tries to get a string property from the options object.
 */
bool
OptionsBase::ParseString(const char* name, nsCString& prop)
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if (!value.isString()) {
        JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
        return false;
    }

    char* tmp = JS_EncodeString(mCx, value.toString());
    NS_ENSURE_TRUE(tmp, false);
    prop.Assign(tmp, strlen(tmp));
    js_free(tmp);
    return true;
}

/*
 * Helper that tries to get a string property from the options object.
 */
bool
OptionsBase::ParseString(const char* name, nsString& prop)
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if (!value.isString()) {
        JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
        return false;
    }

    nsAutoJSString strVal;
    if (!strVal.init(mCx, value.toString()))
        return false;

    prop = strVal;
    return true;
}

/*
 * Helper that tries to get jsid property from the options object.
 */
bool
OptionsBase::ParseId(const char* name, MutableHandleId prop)
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    return JS_ValueToId(mCx, value, prop);
}

/*
 * Helper that tries to get a uint32_t property from the options object.
 */
bool
OptionsBase::ParseUInt32(const char* name, uint32_t* prop)
{
    MOZ_ASSERT(prop);
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue(name, &value, &found);
    NS_ENSURE_TRUE(ok, false);

    if (!found)
        return true;

    if(!JS::ToUint32(mCx, value, prop)) {
        JS_ReportErrorASCII(mCx, "Expected a uint32_t value for property %s", name);
        return false;
    }

    return true;
}

/*
 * Helper that tries to get a list of DOM constructors and other helpers from the options object.
 */
bool
SandboxOptions::ParseGlobalProperties()
{
    RootedValue value(mCx);
    bool found;
    bool ok = ParseValue("wantGlobalProperties", &value, &found);
    NS_ENSURE_TRUE(ok, false);
    if (!found)
        return true;

    if (!value.isObject()) {
        JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties");
        return false;
    }

    RootedObject ctors(mCx, &value.toObject());
    bool isArray;
    if (!JS_IsArrayObject(mCx, ctors, &isArray))
        return false;
    if (!isArray) {
        JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties");
        return false;
    }

    return globalProperties.Parse(mCx, ctors);
}

/*
 * Helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options).
 */
bool
SandboxOptions::Parse()
{
    /* All option names must be ASCII-only. */
    bool ok = ParseObject("sandboxPrototype", &proto) &&
              ParseBoolean("wantXrays", &wantXrays) &&
              ParseBoolean("allowWaivers", &allowWaivers) &&
              ParseBoolean("wantComponents", &wantComponents) &&
              ParseBoolean("wantExportHelpers", &wantExportHelpers) &&
              ParseBoolean("isWebExtensionContentScript", &isWebExtensionContentScript) &&
              ParseBoolean("waiveInterposition", &waiveInterposition) &&
              ParseString("sandboxName", sandboxName) &&
              ParseObject("sameZoneAs", &sameZoneAs) &&
              ParseBoolean("freshZone", &freshZone) &&
              ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
              ParseBoolean("discardSource", &discardSource) &&
              ParseJSString("addonId", &addonId) &&
              ParseBoolean("writeToGlobalPrototype", &writeToGlobalPrototype) &&
              ParseGlobalProperties() &&
              ParseValue("metadata", &metadata) &&
              ParseUInt32("userContextId", &userContextId) &&
              ParseObject("originAttributes", &originAttributes);
    if (!ok)
        return false;

    if (freshZone && sameZoneAs) {
        JS_ReportErrorASCII(mCx, "Cannot use both sameZoneAs and freshZone");
        return false;
    }

    return true;
}

static nsresult
AssembleSandboxMemoryReporterName(JSContext* cx, nsCString& sandboxName)
{
    // Use a default name when the caller did not provide a sandboxName.
    if (sandboxName.IsEmpty())
        sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]");

    nsXPConnect* xpc = nsXPConnect::XPConnect();
    // Get the xpconnect native call context.
    nsAXPCNativeCallContext* cc = nullptr;
    xpc->GetCurrentNativeCallContext(&cc);
    NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG);

    // Get the current source info from xpc.
    nsCOMPtr<nsIStackFrame> frame;
    xpc->GetCurrentJSStack(getter_AddRefs(frame));

    // Append the caller's location information.
    if (frame) {
        nsString location;
        int32_t lineNumber = 0;
        frame->GetFilename(cx, location);
        frame->GetLineNumber(cx, &lineNumber);

        sandboxName.AppendLiteral(" (from: ");
        sandboxName.Append(NS_ConvertUTF16toUTF8(location));
        sandboxName.Append(':');
        sandboxName.AppendInt(lineNumber);
        sandboxName.Append(')');
    }

    return NS_OK;
}

// static
nsresult
nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
                                               JSContext* cx, HandleObject obj,
                                               const CallArgs& args, bool* _retval)
{
    if (args.length() < 1)
        return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);

    nsresult rv;
    bool ok = false;
    bool calledWithOptions = args.length() > 1;
    if (calledWithOptions && !args[1].isObject())
        return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);

    RootedObject optionsObject(cx, calledWithOptions ? &args[1].toObject()
                                                     : nullptr);

    SandboxOptions options(cx, optionsObject);
    if (calledWithOptions && !options.Parse())
        return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);

    // Make sure to set up principals on the sandbox before initing classes.
    nsCOMPtr<nsIPrincipal> principal;
    nsCOMPtr<nsIExpandedPrincipal> expanded;
    nsCOMPtr<nsISupports> prinOrSop;

    if (args[0].isString()) {
        RootedString str(cx, args[0].toString());
        OriginAttributes attrs;
        if (options.originAttributes) {
            JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes));
            if (!attrs.Init(cx, val)) {
                // The originAttributes option, if specified, must be valid!
                JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object");
                return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
            }
        }
        attrs.mUserContextId = options.userContextId;
        ok = ParsePrincipal(cx, str, attrs, getter_AddRefs(principal));
        prinOrSop = principal;
    } else if (args[0].isObject()) {
        RootedObject obj(cx, &args[0].toObject());
        bool isArray;
        if (!JS_IsArrayObject(cx, obj, &isArray)) {
            ok = false;
        } else if (isArray) {
            if (options.userContextId != 0) {
                // We don't support passing a userContextId with an array.
                ok = false;
            } else {
                ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded));
                prinOrSop = expanded;
            }
        } else {
            ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
        }
    } else if (args[0].isNull()) {
        // Null means that we just pass prinOrSop = nullptr, and get an
        // nsNullPrincipal.
        ok = true;
    }

    if (!ok)
        return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);


    if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName)))
        return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);

    if (options.metadata.isNullOrUndefined()) {
        // If the caller is running in a sandbox, inherit.
        RootedObject callerGlobal(cx, CurrentGlobalOrNull(cx));
        if (IsSandbox(callerGlobal)) {
            rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata);
            if (NS_WARN_IF(NS_FAILED(rv)))
                return rv;
        }
    }

    rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options);

    if (NS_FAILED(rv))
        return ThrowAndFail(rv, cx, _retval);

    // We have this crazy behavior where wantXrays=false also implies that the
    // returned sandbox is implicitly waived. We've stopped advertising it, but
    // keep supporting it for now.
    if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, args.rval()))
        return NS_ERROR_UNEXPECTED;

    *_retval = true;
    return NS_OK;
}

nsresult
xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg, const nsAString& source,
                   const nsACString& filename, int32_t lineNo,
                   JSVersion jsVersion, MutableHandleValue rval)
{
    JS_AbortIfWrongThread(cx);
    rval.set(UndefinedValue());

    bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
    RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg));
    if (!sandbox || !IsSandbox(sandbox)) {
        return NS_ERROR_INVALID_ARG;
    }

    nsIScriptObjectPrincipal* sop =
        static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(sandbox));
    MOZ_ASSERT(sop, "Invalid sandbox passed");
    SandboxPrivate* priv = static_cast<SandboxPrivate*>(sop);
    nsCOMPtr<nsIPrincipal> prin = sop->GetPrincipal();
    NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE);

    nsAutoCString filenameBuf;
    if (!filename.IsVoid() && filename.Length() != 0) {
        filenameBuf.Assign(filename);
    } else {
        // Default to the spec of the principal.
        nsresult rv = nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf);
        NS_ENSURE_SUCCESS(rv, rv);
        lineNo = 1;
    }

    // We create a separate cx to do the sandbox evaluation. Scope it.
    RootedValue v(cx, UndefinedValue());
    RootedValue exn(cx, UndefinedValue());
    bool ok = true;
    {
        // We're about to evaluate script, so make an AutoEntryScript.
        // This is clearly Gecko-specific and not in any spec.
        mozilla::dom::AutoEntryScript aes(priv, "XPConnect sandbox evaluation");
        JSContext* sandcx = aes.cx();
        JSAutoCompartment ac(sandcx, sandbox);

        JS::CompileOptions options(sandcx);
        options.setFileAndLine(filenameBuf.get(), lineNo)
               .setVersion(jsVersion);
        MOZ_ASSERT(JS_IsGlobalObject(sandbox));
        ok = JS::Evaluate(sandcx, options,
                          PromiseFlatString(source).get(), source.Length(), &v);

        // If the sandbox threw an exception, grab it off the context.
        if (aes.HasException()) {
            if (!aes.StealException(&exn)) {
                return NS_ERROR_OUT_OF_MEMORY;
            }
        }
    }

    //
    // Alright, we're back on the caller's cx. If an error occured, try to
    // wrap and set the exception. Otherwise, wrap the return value.
    //

    if (!ok) {
        // If we end up without an exception, it was probably due to OOM along
        // the way, in which case we thow. Otherwise, wrap it.
        if (exn.isUndefined() || !JS_WrapValue(cx, &exn))
            return NS_ERROR_OUT_OF_MEMORY;

        // Set the exception on our caller's cx.
        JS_SetPendingException(cx, exn);
        return NS_ERROR_FAILURE;
    }

    // Transitively apply Xray waivers if |sb| was waived.
    if (waiveXray) {
        ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v);
    } else {
        ok = JS_WrapValue(cx, &v);
    }
    NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);

    // Whew!
    rval.set(v);
    return NS_OK;
}

nsresult
xpc::GetSandboxAddonId(JSContext* cx, HandleObject sandbox, MutableHandleValue rval)
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(IsSandbox(sandbox));

    JSAddonId* id = JS::AddonIdOfObject(sandbox);
    if (!id) {
        rval.setNull();
        return NS_OK;
    }

    JS::RootedValue idStr(cx, StringValue(JS::StringOfAddonId(id)));
    if (!JS_WrapValue(cx, &idStr))
        return NS_ERROR_UNEXPECTED;

    rval.set(idStr);
    return NS_OK;
}

nsresult
xpc::GetSandboxMetadata(JSContext* cx, HandleObject sandbox, MutableHandleValue rval)
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(IsSandbox(sandbox));

    RootedValue metadata(cx);
    {
      JSAutoCompartment ac(cx, sandbox);
      metadata = JS_GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT);
    }

    if (!JS_WrapValue(cx, &metadata))
        return NS_ERROR_UNEXPECTED;

    rval.set(metadata);
    return NS_OK;
}

nsresult
xpc::SetSandboxMetadata(JSContext* cx, HandleObject sandbox, HandleValue metadataArg)
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(IsSandbox(sandbox));

    RootedValue metadata(cx);

    JSAutoCompartment ac(cx, sandbox);
    if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr))
        return NS_ERROR_UNEXPECTED;

    JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata);

    return NS_OK;
}