js/xpconnect/src/Sandbox.cpp
author Oana Pop Rus <opoprus@mozilla.com>
Mon, 12 Aug 2019 13:37:03 +0300
changeset 487436 9f551fcee77ecd379e7df7d85102f72b6e9ec8ab
parent 487433 9fd7bea2b512cce296dd2d93f024143d40b9d2af
child 487444 c3700a7108e3fd27607a42a9b65eb86df91dbccc
permissions -rw-r--r--
Backed out 4 changesets (bug 1572782) for build bustages at build/src/obj-firefox/dist/include/nsIXPCScriptable.h on a CLOSED TREE Backed out changeset ec9d15c69bc8 (bug 1572782) Backed out changeset 8239e4baa0f4 (bug 1572782) Backed out changeset 9fd7bea2b512 (bug 1572782) Backed out changeset 11d750555fe1 (bug 1572782)

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * The Components.Sandbox object.
 */

#include "AccessCheck.h"
#include "jsfriendapi.h"
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/PropertySpec.h"
#include "js/Proxy.h"
#include "js/SourceText.h"
#include "js/StructuredClone.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsIException.h"  // for nsIStackFrame
#include "nsIScriptContext.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "ExpandedPrincipal.h"
#include "WrapperFactory.h"
#include "xpcprivate.h"
#include "xpc_make_class.h"
#include "XPCWrapper.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/CSSRuleBinding.h"
#include "mozilla/dom/DirectoryBinding.h"
#include "mozilla/dom/DOMParserBinding.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/InspectorUtilsBinding.h"
#include "mozilla/dom/MessageChannelBinding.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/NodeBinding.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/PromiseDebuggingBinding.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/dom/XMLSerializerBinding.h"
#include "mozilla/dom/FormDataBinding.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DeferredFinalize.h"
#include "mozilla/NullPrincipal.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

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) {
  if (!nsJSUtils::DumpEnabled()) {
    return true;
  }

  CallArgs args = CallArgsFromVp(argc, vp);

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

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

  JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
  char* cstr = utf8str.get();
  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);
    }

    JSAutoRealm ar(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;
    }
  }
  JS_MarkCrossZoneIdValue(cx, StringValue(funname));

  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);
  if (!args.computeThis(cx, &thisObject)) {
    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(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;
  }
  dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx)
                                   ? dom::CallerType::System
                                   : dom::CallerType::NonSystem;
  ErrorResult rv;
  RefPtr<dom::Promise> response = FetchRequest(
      global, Constify(request), Constify(options), callerType, 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 scope(cx, JS::CurrentGlobalOrNull(cx));
  if (SandboxFetch(cx, scope, args)) {
    return true;
  }
  return ConvertExceptionToPromise(cx, args.rval());
}

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

  return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
         dom::Request_Binding::GetConstructorObject(cx) &&
         dom::Response_Binding::GetConstructorObject(cx) &&
         dom::Headers_Binding::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());
  // CheckedUnwrapStatic is OK here, since we only care about whether
  // it's a scripted proxy and the things CheckedUnwrapStatic fails on
  // are not.
  obj = js::CheckedUnwrapStatic(obj);
  if (!obj) {
    args.rval().setBoolean(false);
    return true;
  }

  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(obj);
  DestroyProtoAndIfaceCache(obj);
  DeferredFinalize(sop);
}

static size_t sandbox_moved(JSObject* obj, 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) {
    return 0;
  }

  return static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
}

#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT \
  (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)

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

static const js::ClassExtension SandboxClassExtension = {
    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};

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

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

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

nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() {}

NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox,
                        nsIXPCComponents_utils_Sandbox, nsIXPCScriptable)

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

class SandboxProxyHandler : public js::Wrapper {
 public:
  constexpr SandboxProxyHandler() : js::Wrapper(0) {}

  virtual bool getOwnPropertyDescriptor(
      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
      JS::MutableHandle<JS::PropertyDescriptor> desc) const override;

  // We just forward the high-level methods to the BaseProxyHandler versions
  // which implement them in terms of lower-level methods.
  virtual bool has(JSContext* cx, JS::Handle<JSObject*> proxy,
                   JS::Handle<jsid> id, bool* bp) const override;
  virtual bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
                   JS::HandleValue receiver, JS::Handle<jsid> id,
                   JS::MutableHandle<JS::Value> vp) const override;
  virtual bool set(JSContext* cx, JS::Handle<JSObject*> proxy,
                   JS::Handle<jsid> id, JS::Handle<JS::Value> v,
                   JS::Handle<JS::Value> receiver,
                   JS::ObjectOpResult& result) const override;

  virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                      JS::Handle<jsid> id, bool* bp) const override;
  virtual bool getOwnEnumerablePropertyKeys(
      JSContext* cx, JS::Handle<JSObject*> proxy,
      JS::MutableHandleIdVector props) const override;
  virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
                         JS::MutableHandleIdVector props) const override;

 private:
  // Implements the custom getPropertyDescriptor behavior. If the getOwn
  // argument is true we only look for "own" properties.
  bool getPropertyDescriptorImpl(
      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
      bool getOwn, JS::MutableHandle<JS::PropertyDescriptor> desc) const;
};

static const SandboxProxyHandler sandboxProxyHandler;

namespace xpc {

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

}  // namespace xpc

// A proxy handler that lets us wrap callables and invoke them with
// the correct this object, while forwarding all other operations down
// to them directly.
class SandboxCallableProxyHandler : public js::Wrapper {
 public:
  constexpr SandboxCallableProxyHandler() : js::Wrapper(0) {}

  virtual bool call(JSContext* cx, JS::Handle<JSObject*> proxy,
                    const JS::CallArgs& args) const override;

  static const size_t SandboxProxySlot = 0;

  static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy) {
    return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject();
  }
};

static const SandboxCallableProxyHandler sandboxCallableProxyHandler;

bool 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) == &sandboxProxyHandler);

  // The global of the sandboxProxy is the sandbox global, and the
  // target object is the original proto.
  RootedObject sandboxGlobal(cx, JS::GetNonCCWObjectGlobal(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, args.thisv());
  if (isXray) {
    RootedObject thisObject(cx);
    if (!args.computeThis(cx, &thisObject)) {
      return false;
    }
    thisVal.setObject(*thisObject);
  }

  if (thisVal == ObjectValue(*sandboxGlobal)) {
    thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
  }

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

/*
 * 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) == &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, &sandboxCallableProxyHandler, priv,
                                     nullptr, options);
  if (obj) {
    js::SetProxyReservedSlot(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;
}

static bool IsMaybeWrappedDOMConstructor(JSObject* obj) {
  // We really care about the underlying object here, which might be wrapped in
  // cross-compartment wrappers.  CheckedUnwrapStatic is fine, since we just
  // care whether it's a DOM constructor.
  obj = js::CheckedUnwrapStatic(obj);
  if (!obj) {
    return false;
  }

  return dom::IsDOMConstructor(obj);
}

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

  MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy));

  if (getOwn) {
    if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc)) {
      return false;
    }
  } else {
    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) &&
        // Don't wrap DOM constructors: they don't care about the "this"
        // they're invoked with anyway, being constructors.  And if we wrap
        // them here we break invariants like Node == Node and whatnot.
        !IsMaybeWrappedDOMConstructor(val)) {
      val = WrapCallable(cx, val, proxy);
      if (!val) {
        return false;
      }
      desc.value().setObject(*val);
    }
  }

  return true;
}

bool SandboxProxyHandler::getOwnPropertyDescriptor(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
    JS::MutableHandle<PropertyDescriptor> desc) const {
  return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc);
}

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

bool SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<jsid> id, bool* bp) const {
  // This uses JS_GetPropertyDescriptorById for backward compatibility.
  Rooted<PropertyDescriptor> desc(cx);
  if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) {
    return false;
  }

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

bool SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy,
                              JS::Handle<JS::Value> receiver,
                              JS::Handle<jsid> id,
                              JS::MutableHandle<Value> vp) const {
  // This uses JS_GetPropertyDescriptorById for backward compatibility.
  Rooted<PropertyDescriptor> desc(cx);
  if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &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 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 SandboxProxyHandler::getOwnEnumerablePropertyKeys(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    MutableHandleIdVector props) const {
  return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
}

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

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;
    }
    JSFlatString* nameStr = JS_FlattenString(cx, nameValue.toString());
    if (!nameStr) {
      return false;
    }
    if (JS_FlatStringEqualsAscii(nameStr, "Blob")) {
      Blob = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "ChromeUtils")) {
      ChromeUtils = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "CSS")) {
      CSS = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "CSSRule")) {
      CSSRule = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "Directory")) {
      Directory = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "DOMParser")) {
      DOMParser = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "Element")) {
      Element = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "Event")) {
      Event = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "File")) {
      File = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "FileReader")) {
      FileReader = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "FormData")) {
      FormData = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "InspectorUtils")) {
      InspectorUtils = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "MessageChannel")) {
      MessageChannel = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "Node")) {
      Node = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "NodeFilter")) {
      NodeFilter = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "PromiseDebugging")) {
      PromiseDebugging = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "TextDecoder")) {
      TextDecoder = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "TextEncoder")) {
      TextEncoder = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "URL")) {
      URL = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "URLSearchParams")) {
      URLSearchParams = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "XMLHttpRequest")) {
      XMLHttpRequest = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "XMLSerializer")) {
      XMLSerializer = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "atob")) {
      atob = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "btoa")) {
      btoa = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "caches")) {
      caches = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "crypto")) {
      crypto = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "fetch")) {
      fetch = true;
    } else if (JS_FlatStringEqualsAscii(nameStr, "indexedDB")) {
      indexedDB = true;
#ifdef MOZ_WEBRTC
    } else if (JS_FlatStringEqualsAscii(nameStr, "rtcIdentityProvider")) {
      rtcIdentityProvider = true;
#endif
    } else {
      RootedString nameStr(cx, nameValue.toString());
      JS::UniqueChars name = JS_EncodeStringToUTF8(cx, nameStr);
      if (!name) {
        return false;
      }

      JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.get());
      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 (Blob && !dom::Blob_Binding::GetConstructorObject(cx)) return false;

  if (ChromeUtils && !dom::ChromeUtils_Binding::GetConstructorObject(cx)) {
    return false;
  }

  if (CSS && !dom::CSS_Binding::GetConstructorObject(cx)) {
    return false;
  }

  if (CSSRule && !dom::CSSRule_Binding::GetConstructorObject(cx)) {
    return false;
  }

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

  if (DOMParser && !dom::DOMParser_Binding::GetConstructorObject(cx))
    return false;

  if (Element && !dom::Element_Binding::GetConstructorObject(cx)) return false;

  if (Event && !dom::Event_Binding::GetConstructorObject(cx)) return false;

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

  if (FileReader && !dom::FileReader_Binding::GetConstructorObject(cx)) {
    return false;
  }

  if (FormData && !dom::FormData_Binding::GetConstructorObject(cx))
    return false;

  if (InspectorUtils && !dom::InspectorUtils_Binding::GetConstructorObject(cx))
    return false;

  if (MessageChannel &&
      (!dom::MessageChannel_Binding::GetConstructorObject(cx) ||
       !dom::MessagePort_Binding::GetConstructorObject(cx)))
    return false;

  if (Node && !dom::Node_Binding::GetConstructorObject(cx)) {
    return false;
  }

  if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) {
    return false;
  }

  if (PromiseDebugging &&
      !dom::PromiseDebugging_Binding::GetConstructorObject(cx)) {
    return false;
  }

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

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

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

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

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

  if (XMLSerializer && !dom::XMLSerializer_Binding::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 (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) {
    return false;
  }

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

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

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

  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<NullPrincipal> nullPrin =
          NullPrincipal::CreateWithoutOriginAttributes();
      principal = nullPrin;
    }
  }
  MOZ_ASSERT(principal);

  JS::RealmOptions realmOptions;

  auto& creationOptions = realmOptions.creationOptions();

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

  bool isSystemPrincipal = principal->IsSystemPrincipal();
  if (isSystemPrincipal) {
    creationOptions.setClampAndJitterTime(false);
  }

  xpc::SetPrefableRealmOptions(realmOptions);
  if (options.sameZoneAs) {
    creationOptions.setNewCompartmentInExistingZone(
        js::UncheckedUnwrap(options.sameZoneAs));
  } else if (options.freshZone) {
    creationOptions.setNewCompartmentAndZone();
  } else if (isSystemPrincipal && !options.invisibleToDebugger &&
             !options.freshCompartment) {
    // Use a shared system compartment for system-principal sandboxes that don't
    // require invisibleToDebugger (this is a compartment property, see bug
    // 1482215).
    creationOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
  } else {
    creationOptions.setNewCompartmentInSystemZone();
  }

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

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

  const js::Class* clasp = &SandboxClass;

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

  // Use exclusive expandos for non-system-principal sandboxes.
  bool hasExclusiveExpandos = !isSystemPrincipal;

  // 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.
  bool wantXrays = AccessCheck::isChrome(sandbox) ? false : options.wantXrays;

  if (creationOptions.compartmentSpecifier() ==
      JS::CompartmentSpecifier::ExistingCompartment) {
    // Make sure the compartment we're reusing has flags that match what we
    // would set on a new compartment.
    CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
    MOZ_RELEASE_ASSERT(priv->allowWaivers == options.allowWaivers);
    MOZ_RELEASE_ASSERT(priv->isWebExtensionContentScript ==
                       options.isWebExtensionContentScript);
    MOZ_RELEASE_ASSERT(priv->isContentXBLCompartment ==
                       options.isContentXBLScope);
    MOZ_RELEASE_ASSERT(priv->isUAWidgetCompartment == options.isUAWidgetScope);
    MOZ_RELEASE_ASSERT(priv->hasExclusiveExpandos == hasExclusiveExpandos);
    MOZ_RELEASE_ASSERT(priv->wantXrays == wantXrays);
  } else {
    CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
    priv->allowWaivers = options.allowWaivers;
    priv->isWebExtensionContentScript = options.isWebExtensionContentScript;
    priv->isContentXBLCompartment = options.isContentXBLScope;
    priv->isUAWidgetCompartment = options.isUAWidgetScope;
    priv->hasExclusiveExpandos = hasExclusiveExpandos;
    priv->wantXrays = wantXrays;
  }

  {
    JSAutoRealm ar(cx, sandbox);

    // This creates a SandboxPrivate and passes ownership of it to |sandbox|.
    SandboxPrivate::Create(principal, sandbox);

    // Ensure |Object.prototype| is instantiated before prototype-
    // splicing below.
    if (!JS::GetRealmObjectPrototype(cx)) {
      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) {
        // We just wrapped options.proto into the compartment of whatever Realm
        // is on the cx, so use that same realm for the CheckedUnwrapDynamic
        // call.
        JSObject* unwrappedProto =
            js::CheckedUnwrapDynamic(options.proto, cx, 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, &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;
      }
    }

    bool allowComponents = principal->IsSystemPrincipal();
    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);

  JSAutoRealm ar(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 Content Principal for the sandbox.
 */
bool ParsePrincipal(JSContext* cx, HandleString contentUrl,
                    const OriginAttributes& aAttrs, nsIPrincipal** principal) {
  MOZ_ASSERT(principal);
  MOZ_ASSERT(contentUrl);
  nsCOMPtr<nsIURI> uri;
  nsAutoJSString contentStr;
  NS_ENSURE_TRUE(contentStr.init(cx, contentUrl), false);
  nsresult rv = NS_NewURI(getter_AddRefs(uri), contentStr);
  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::CreateContentPrincipal(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;

  // We might have a Window here, so need ReflectorToISupportsDynamic
  nsCOMPtr<nsISupports> native = ReflectorToISupportsDynamic(from, cx);

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

    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 = principal->IsSystemPrincipal();
      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 content 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());
    }
  }

  RefPtr<ExpandedPrincipal> result =
      ExpandedPrincipal::Create(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;
  }

  JS::UniqueChars tmp = JS_EncodeStringToLatin1(mCx, value.toString());
  NS_ENSURE_TRUE(tmp, false);
  prop.Assign(tmp.get(), strlen(tmp.get()));
  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) &&
            ParseString("sandboxName", sandboxName) &&
            ParseObject("sameZoneAs", &sameZoneAs) &&
            ParseBoolean("freshCompartment", &freshCompartment) &&
            ParseBoolean("freshZone", &freshZone) &&
            ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
            ParseBoolean("discardSource", &discardSource) &&
            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]");
  } else {
#ifndef DEBUG
    // Adding the caller location is fairly expensive, so in non-debug
    // builds, only add it if we don't have an explicit sandbox name.
    return NS_OK;
#endif
  }

  // Get the xpconnect native call context.
  XPCCallContext* cc = XPCJSContext::Get()->GetCallContext();
  NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG);

  // Get the current source info from xpc.
  nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();

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

    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
    // NullPrincipal.
    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, JS::GetScriptedCallerGlobal(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);
  }

  *_retval = true;
  return NS_OK;
}

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

  bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
  // CheckedUnwrapStatic is fine here, since we're checking for "is it a
  // sandbox".
  RootedObject sandbox(cx, js::CheckedUnwrapStatic(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();
    JSAutoRealm ar(sandcx, sandbox);

    JS::CompileOptions options(sandcx);
    options.setFileAndLine(filenameBuf.get(), lineNo);
    MOZ_ASSERT(JS_IsGlobalObject(sandbox));

    const nsPromiseFlatString& flat = PromiseFlatString(source);

    JS::SourceText<char16_t> buffer;
    ok = buffer.init(sandcx, flat.get(), flat.Length(),
                     JS::SourceOwnership::Borrowed) &&
         JS::Evaluate(sandcx, options, buffer, &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::GetSandboxMetadata(JSContext* cx, HandleObject sandbox,
                                 MutableHandleValue rval) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(IsSandbox(sandbox));

  RootedValue metadata(cx);
  {
    JSAutoRealm ar(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);

  JSAutoRealm ar(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;
}