/* -*- 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" xpcom objects for JavaScript. */

#include "xpcprivate.h"
#include "xpc_make_class.h"
#include "XPCJSWeakReference.h"
#include "AccessCheck.h"
#include "WrapperFactory.h"
#include "nsJSUtils.h"
#include "mozJSModuleLoader.h"
#include "nsContentUtils.h"
#include "nsCycleCollector.h"
#include "jsfriendapi.h"
#include "js/Array.h"  // JS::IsArrayObject
#include "js/CallAndConstruct.h"  // JS::IsCallable, JS_CallFunctionName, JS_CallFunctionValue
#include "js/CharacterEncoding.h"
#include "js/friend/WindowProxy.h"  // js::ToWindowProxyIfWindow
#include "js/Object.h"              // JS::GetClass, JS::GetCompartment
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty, JS_SetPropertyById
#include "js/SavedFrameAPI.h"
#include "js/StructuredClone.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Attributes.h"
#include "mozilla/LoadContext.h"
#include "mozilla/Preferences.h"
#include "nsJSEnvironment.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Try.h"
#include "mozilla/URLPreloader.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WindowBinding.h"
#include "nsZipArchive.h"
#include "nsWindowMemoryReporter.h"
#include "nsICycleCollectorListener.h"
#include "nsIException.h"
#include "nsIScriptError.h"
#include "nsPIDOMWindow.h"
#include "nsGlobalWindowInner.h"
#include "nsScriptError.h"
#include "GeckoProfiler.h"
#include "ProfilerControl.h"
#include "mozilla/EditorSpellCheck.h"
#include "nsCommandLine.h"
#include "nsCommandParams.h"
#include "nsPersistentProperties.h"
#include "nsIDocumentEncoder.h"

using namespace mozilla;
using namespace JS;
using namespace js;
using namespace xpc;
using mozilla::dom::Exception;

/***************************************************************************/
// stuff used by all

nsresult xpc::ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval) {
  XPCThrower::Throw(errNum, cx);
  *retval = false;
  return NS_OK;
}

static bool JSValIsInterfaceOfType(JSContext* cx, HandleValue v, REFNSIID iid) {
  nsCOMPtr<nsIXPConnectWrappedNative> wn;
  nsCOMPtr<nsISupports> iface;

  if (v.isPrimitive()) {
    return false;
  }

  nsIXPConnect* xpc = nsIXPConnect::XPConnect();
  RootedObject obj(cx, &v.toObject());
  return NS_SUCCEEDED(
             xpc->GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wn))) &&
         wn &&
         NS_SUCCEEDED(
             wn->Native()->QueryInterface(iid, getter_AddRefs(iface))) &&
         iface;
}

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

class nsXPCComponents_Interfaces final : public nsIXPCComponents_Interfaces,
                                         public nsIXPCScriptable,
                                         public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_INTERFACES
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_Interfaces();

 private:
  virtual ~nsXPCComponents_Interfaces();
};

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Interfaces),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_Interfaces");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_Interfaces::nsXPCComponents_Interfaces() = default;

nsXPCComponents_Interfaces::~nsXPCComponents_Interfaces() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_Interfaces, nsIXPCComponents_Interfaces,
                  nsIXPCScriptable, nsIClassInfo);

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Interfaces
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Interfaces"
#define XPC_MAP_FLAGS                                               \
  (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_Interfaces::NewEnumerate(nsIXPConnectWrappedNative* wrapper,
                                         JSContext* cx, JSObject* obj,
                                         JS::MutableHandleIdVector properties,
                                         bool enumerableOnly, bool* _retval) {
  if (!properties.reserve(nsXPTInterfaceInfo::InterfaceCount())) {
    *_retval = false;
    return NS_OK;
  }

  for (uint32_t index = 0; index < nsXPTInterfaceInfo::InterfaceCount();
       index++) {
    const nsXPTInterfaceInfo* interface = nsXPTInterfaceInfo::ByIndex(index);
    if (!interface) {
      continue;
    }

    const char* name = interface->Name();
    if (!name) {
      continue;
    }

    RootedString idstr(cx, JS_NewStringCopyZ(cx, name));
    if (!idstr) {
      *_retval = false;
      return NS_OK;
    }

    RootedId id(cx);
    if (!JS_StringToId(cx, idstr, &id)) {
      *_retval = false;
      return NS_OK;
    }

    properties.infallibleAppend(id);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Interfaces::Resolve(nsIXPConnectWrappedNative* wrapper,
                                    JSContext* cx, JSObject* objArg, jsid idArg,
                                    bool* resolvedp, bool* _retval) {
  RootedObject obj(cx, objArg);
  RootedId id(cx, idArg);

  if (!id.isString()) {
    return NS_OK;
  }

  RootedString str(cx, id.toString());
  JS::UniqueChars name = JS_EncodeStringToLatin1(cx, str);

  // we only allow interfaces by name here
  if (name && name[0] != '{') {
    const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByName(name.get());
    if (!info) {
      return NS_OK;
    }

    RootedValue iidv(cx);
    if (xpc::IfaceID2JSValue(cx, *info, &iidv)) {
      *resolvedp = true;
      *_retval = JS_DefinePropertyById(cx, obj, id, iidv,
                                       JSPROP_ENUMERATE | JSPROP_READONLY |
                                           JSPROP_PERMANENT | JSPROP_RESOLVING);
    }
  }
  return NS_OK;
}

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

class nsXPCComponents_Classes final : public nsIXPCComponents_Classes,
                                      public nsIXPCScriptable,
                                      public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_CLASSES
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_Classes();

 private:
  virtual ~nsXPCComponents_Classes();
};

/***************************************************************************/
NS_IMETHODIMP
nsXPCComponents_Classes::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Classes),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_Classes");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_Classes::nsXPCComponents_Classes() = default;

nsXPCComponents_Classes::~nsXPCComponents_Classes() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_Classes, nsIXPCComponents_Classes,
                  nsIXPCScriptable, nsIClassInfo)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Classes
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Classes"
#define XPC_MAP_FLAGS                                               \
  (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_Classes::NewEnumerate(nsIXPConnectWrappedNative* wrapper,
                                      JSContext* cx, JSObject* obj,
                                      JS::MutableHandleIdVector properties,
                                      bool enumerableOnly, bool* _retval) {
  nsCOMPtr<nsIComponentRegistrar> compMgr;
  if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) ||
      !compMgr) {
    return NS_ERROR_UNEXPECTED;
  }

  nsTArray<nsCString> contractIDs;
  if (NS_FAILED(compMgr->GetContractIDs(contractIDs))) {
    return NS_ERROR_UNEXPECTED;
  }

  for (const auto& name : contractIDs) {
    RootedString idstr(cx, JS_NewStringCopyN(cx, name.get(), name.Length()));
    if (!idstr) {
      *_retval = false;
      return NS_OK;
    }

    RootedId id(cx);
    if (!JS_StringToId(cx, idstr, &id)) {
      *_retval = false;
      return NS_OK;
    }

    if (!properties.append(id)) {
      *_retval = false;
      return NS_OK;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Classes::Resolve(nsIXPConnectWrappedNative* wrapper,
                                 JSContext* cx, JSObject* objArg, jsid idArg,
                                 bool* resolvedp, bool* _retval)

{
  RootedId id(cx, idArg);
  RootedObject obj(cx, objArg);

  RootedValue cidv(cx);
  if (id.isString() && xpc::ContractID2JSValue(cx, id.toString(), &cidv)) {
    *resolvedp = true;
    *_retval = JS_DefinePropertyById(cx, obj, id, cidv,
                                     JSPROP_ENUMERATE | JSPROP_READONLY |
                                         JSPROP_PERMANENT | JSPROP_RESOLVING);
  }
  return NS_OK;
}

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

// Currently the possible results do not change at runtime, so they are only
// cached once (unlike ContractIDs, CLSIDs, and IIDs)

class nsXPCComponents_Results final : public nsIXPCComponents_Results,
                                      public nsIXPCScriptable,
                                      public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_RESULTS
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_Results();

 private:
  virtual ~nsXPCComponents_Results();
};

/***************************************************************************/
NS_IMETHODIMP
nsXPCComponents_Results::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Results),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_Results");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_Results::nsXPCComponents_Results() = default;

nsXPCComponents_Results::~nsXPCComponents_Results() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_Results, nsIXPCComponents_Results,
                  nsIXPCScriptable, nsIClassInfo)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Results
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Results"
#define XPC_MAP_FLAGS                                               \
  (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_Results::NewEnumerate(nsIXPConnectWrappedNative* wrapper,
                                      JSContext* cx, JSObject* obj,
                                      JS::MutableHandleIdVector properties,
                                      bool enumerableOnly, bool* _retval) {
  const char* name;
  const void* iter = nullptr;
  while (nsXPCException::IterateNSResults(nullptr, &name, nullptr, &iter)) {
    RootedString idstr(cx, JS_NewStringCopyZ(cx, name));
    if (!idstr) {
      *_retval = false;
      return NS_OK;
    }

    RootedId id(cx);
    if (!JS_StringToId(cx, idstr, &id)) {
      *_retval = false;
      return NS_OK;
    }

    if (!properties.append(id)) {
      *_retval = false;
      return NS_OK;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Results::Resolve(nsIXPConnectWrappedNative* wrapper,
                                 JSContext* cx, JSObject* objArg, jsid idArg,
                                 bool* resolvedp, bool* _retval) {
  RootedObject obj(cx, objArg);
  RootedId id(cx, idArg);
  if (!id.isString()) {
    return NS_OK;
  }

  JS::UniqueChars name = JS_EncodeStringToLatin1(cx, id.toString());
  if (name) {
    const char* rv_name;
    const void* iter = nullptr;
    nsresult rv;
    while (nsXPCException::IterateNSResults(&rv, &rv_name, nullptr, &iter)) {
      if (!strcmp(name.get(), rv_name)) {
        *resolvedp = true;
        if (!JS_DefinePropertyById(cx, obj, id, (uint32_t)rv,
                                   JSPROP_ENUMERATE | JSPROP_READONLY |
                                       JSPROP_PERMANENT | JSPROP_RESOLVING)) {
          return NS_ERROR_UNEXPECTED;
        }
      }
    }
  }
  return NS_OK;
}

/***************************************************************************/
// JavaScript Constructor for nsIJSID objects (Components.ID)

class nsXPCComponents_ID final : public nsIXPCComponents_ID,
                                 public nsIXPCScriptable,
                                 public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_ID
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_ID();

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

/***************************************************************************/
NS_IMETHODIMP
nsXPCComponents_ID::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_ID),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_ID");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_ID::nsXPCComponents_ID() = default;

nsXPCComponents_ID::~nsXPCComponents_ID() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_ID, nsIXPCComponents_ID, nsIXPCScriptable,
                  nsIClassInfo)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_ID
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ID"
#define XPC_MAP_FLAGS                                         \
  (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \
   XPC_SCRIPTABLE_WANT_HASINSTANCE |                          \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_ID::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_ID::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
                              JSObject* objArg, const CallArgs& args,
                              bool* _retval) {
  RootedObject obj(cx, objArg);
  return CallOrConstruct(wrapper, cx, obj, args, _retval);
}

// static
nsresult nsXPCComponents_ID::CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
                                             JSContext* cx, HandleObject obj,
                                             const CallArgs& args,
                                             bool* _retval) {
  // make sure we have at least one arg

  if (args.length() < 1) {
    return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);
  }

  // Prevent non-chrome code from creating ID objects.
  if (!nsContentUtils::IsCallerChrome()) {
    return ThrowAndFail(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, cx, _retval);
  }

  // convert the first argument into a string and see if it looks like an id

  JSString* jsstr = ToString(cx, args[0]);
  if (!jsstr) {
    return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval);
  }

  JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, jsstr);
  if (!bytes) {
    return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval);
  }

  nsID id;
  if (!id.Parse(bytes.get())) {
    return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval);
  }

  // make the new object and return it.

  if (!xpc::ID2JSValue(cx, id, args.rval())) {
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_ID::HasInstance(nsIXPConnectWrappedNative* wrapper,
                                JSContext* cx, JSObject* obj, HandleValue val,
                                bool* bp, bool* _retval) {
  if (bp) {
    *bp = xpc::JSValue2ID(cx, val).isSome();
  }
  return NS_OK;
}

/***************************************************************************/
// JavaScript Constructor for Exception objects (Components.Exception)

class nsXPCComponents_Exception final : public nsIXPCComponents_Exception,
                                        public nsIXPCScriptable,
                                        public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_EXCEPTION
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_Exception();

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

/***************************************************************************/
NS_IMETHODIMP
nsXPCComponents_Exception::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Exception),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_Exception");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_Exception::nsXPCComponents_Exception() = default;

nsXPCComponents_Exception::~nsXPCComponents_Exception() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_Exception, nsIXPCComponents_Exception,
                  nsIXPCScriptable, nsIClassInfo)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Exception
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Exception"
#define XPC_MAP_FLAGS                                         \
  (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \
   XPC_SCRIPTABLE_WANT_HASINSTANCE |                          \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_Exception::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_Exception::Construct(nsIXPConnectWrappedNative* wrapper,
                                     JSContext* cx, JSObject* objArg,
                                     const CallArgs& args, bool* _retval) {
  RootedObject obj(cx, objArg);
  return CallOrConstruct(wrapper, cx, obj, args, _retval);
}

struct MOZ_STACK_CLASS ExceptionArgParser {
  ExceptionArgParser(JSContext* context, nsIXPConnect* xpconnect)
      : eMsg("exception"),
        eResult(NS_ERROR_FAILURE),
        cx(context),
        xpc(xpconnect) {}

  // Public exception parameter values. During construction, these are
  // initialized to the appropriate defaults.
  const char* eMsg;
  nsresult eResult;
  nsCOMPtr<nsIStackFrame> eStack;
  nsCOMPtr<nsISupports> eData;

  // Parse the constructor arguments into the above |eFoo| parameter values.
  bool parse(const CallArgs& args) {
    /*
     * The Components.Exception takes a series of arguments, all of them
     * optional:
     *
     * Argument 0: Exception message (defaults to 'exception').
     * Argument 1: Result code (defaults to NS_ERROR_FAILURE) _or_ options
     *             object (see below).
     * Argument 2: Stack (defaults to the current stack, which we trigger
     *                    by leaving this nullptr in the parser).
     * Argument 3: Optional user data (defaults to nullptr).
     *
     * To dig our way out of this clunky API, we now support passing an
     * options object as the second parameter (as opposed to a result code).
     * If this is the case, all subsequent arguments are ignored, and the
     * following properties are parsed out of the object (using the
     * associated default if the property does not exist):
     *
     *   result:    Result code (see argument 1).
     *   stack:     Call stack (see argument 2).
     *   data:      User data (see argument 3).
     */
    if (args.length() > 0 && !parseMessage(args[0])) {
      return false;
    }
    if (args.length() > 1) {
      if (args[1].isObject()) {
        RootedObject obj(cx, &args[1].toObject());
        return parseOptionsObject(obj);
      }
      if (!parseResult(args[1])) {
        return false;
      }
    }
    if (args.length() > 2) {
      if (!parseStack(args[2])) {
        return false;
      }
    }
    if (args.length() > 3) {
      if (!parseData(args[3])) {
        return false;
      }
    }
    return true;
  }

 protected:
  /*
   * Parsing helpers.
   */

  bool parseMessage(HandleValue v) {
    JSString* str = ToString(cx, v);
    if (!str) {
      return false;
    }
    messageBytes = JS_EncodeStringToLatin1(cx, str);
    eMsg = messageBytes.get();
    return !!eMsg;
  }

  bool parseResult(HandleValue v) {
    return JS::ToUint32(cx, v, (uint32_t*)&eResult);
  }

  bool parseStack(HandleValue v) {
    if (!v.isObject()) {
      // eStack has already been initialized to null, which is what we want
      // for any non-object values (including null).
      return true;
    }

    RootedObject stackObj(cx, &v.toObject());
    return NS_SUCCEEDED(xpc->WrapJS(cx, stackObj, NS_GET_IID(nsIStackFrame),
                                    getter_AddRefs(eStack)));
  }

  bool parseData(HandleValue v) {
    if (!v.isObject()) {
      // eData has already been initialized to null, which is what we want
      // for any non-object values (including null).
      return true;
    }

    RootedObject obj(cx, &v.toObject());
    return NS_SUCCEEDED(
        xpc->WrapJS(cx, obj, NS_GET_IID(nsISupports), getter_AddRefs(eData)));
  }

  bool parseOptionsObject(HandleObject obj) {
    RootedValue v(cx);

    if (!getOption(obj, "result", &v) || (!v.isUndefined() && !parseResult(v)))
      return false;

    if (!getOption(obj, "stack", &v) || (!v.isUndefined() && !parseStack(v)))
      return false;

    if (!getOption(obj, "data", &v) || (!v.isUndefined() && !parseData(v)))
      return false;

    return true;
  }

  bool getOption(HandleObject obj, const char* name, MutableHandleValue rv) {
    // Look for the property.
    bool found;
    if (!JS_HasProperty(cx, obj, name, &found)) {
      return false;
    }

    // If it wasn't found, indicate with undefined.
    if (!found) {
      rv.setUndefined();
      return true;
    }

    // Get the property.
    return JS_GetProperty(cx, obj, name, rv);
  }

  /*
   * Internal data members.
   */

  // If there's a non-default exception string, hold onto the allocated bytes.
  JS::UniqueChars messageBytes;

  // Various bits and pieces that are helpful to have around.
  JSContext* cx;
  nsIXPConnect* xpc;
};

// static
nsresult nsXPCComponents_Exception::CallOrConstruct(
    nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj,
    const CallArgs& args, bool* _retval) {
  nsIXPConnect* xpc = nsIXPConnect::XPConnect();

  MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsCallerChrome());

  // Parse the arguments to the Exception constructor.
  ExceptionArgParser parser(cx, xpc);
  if (!parser.parse(args)) {
    return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval);
  }

  RefPtr<Exception> e = new Exception(nsCString(parser.eMsg), parser.eResult,
                                      ""_ns, parser.eStack, parser.eData);

  RootedObject newObj(cx);
  if (NS_FAILED(xpc->WrapNative(cx, obj, e, NS_GET_IID(nsIException),
                                newObj.address())) ||
      !newObj) {
    return ThrowAndFail(NS_ERROR_XPC_CANT_CREATE_WN, cx, _retval);
  }

  args.rval().setObject(*newObj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Exception::HasInstance(nsIXPConnectWrappedNative* wrapper,
                                       JSContext* cx, JSObject* obj,
                                       HandleValue val, bool* bp,
                                       bool* _retval) {
  using namespace mozilla::dom;

  if (bp) {
    *bp = (val.isObject() && IS_INSTANCE_OF(Exception, &val.toObject())) ||
          JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIException));
  }
  return NS_OK;
}

/*******************************************************/
// JavaScript Constructor for nsIXPCConstructor objects (Components.Constructor)

class nsXPCComponents_Constructor final : public nsIXPCComponents_Constructor,
                                          public nsIXPCScriptable,
                                          public nsIClassInfo {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCCOMPONENTS_CONSTRUCTOR
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSICLASSINFO

 public:
  nsXPCComponents_Constructor();

 private:
  virtual ~nsXPCComponents_Constructor();
  static bool InnerConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
  static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
                                  JSContext* cx, HandleObject obj,
                                  const CallArgs& args, bool* _retval);
};

/***************************************************************************/
NS_IMETHODIMP
nsXPCComponents_Constructor::GetInterfaces(nsTArray<nsIID>& aArray) {
  aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Constructor),
                           NS_GET_IID(nsIXPCScriptable)};
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetScriptableHelper(nsIXPCScriptable** retval) {
  *retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetClassDescription(
    nsACString& aClassDescription) {
  aClassDescription.AssignLiteral("XPCComponents_Constructor");
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

nsXPCComponents_Constructor::nsXPCComponents_Constructor() = default;

nsXPCComponents_Constructor::~nsXPCComponents_Constructor() {
  // empty
}

NS_IMPL_ISUPPORTS(nsXPCComponents_Constructor, nsIXPCComponents_Constructor,
                  nsIXPCScriptable, nsIClassInfo)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Constructor
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Constructor"
#define XPC_MAP_FLAGS                                         \
  (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \
   XPC_SCRIPTABLE_WANT_HASINSTANCE |                          \
   XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
#include "xpc_map_end.h" /* This will #undef the above */

// static
bool nsXPCComponents_Constructor::InnerConstructor(JSContext* cx, unsigned argc,
                                                   JS::Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedObject callee(cx, &args.callee());

  // Fetch the property name ids, so we can look them up.
  XPCJSRuntime* runtime = XPCJSRuntime::Get();
  HandleId classIDProp = runtime->GetStringID(XPCJSContext::IDX_CLASS_ID);
  HandleId interfaceIDProp =
      runtime->GetStringID(XPCJSContext::IDX_INTERFACE_ID);
  HandleId initializerProp =
      runtime->GetStringID(XPCJSContext::IDX_INITIALIZER);

  // Get properties ('classID', 'interfaceID', and 'initializer') off the
  // constructor object.
  RootedValue classIDv(cx);
  RootedValue interfaceID(cx);
  RootedValue initializer(cx);
  if (!JS_GetPropertyById(cx, callee, classIDProp, &classIDv) ||
      !JS_GetPropertyById(cx, callee, interfaceIDProp, &interfaceID) ||
      !JS_GetPropertyById(cx, callee, initializerProp, &initializer)) {
    return false;
  }
  if (!classIDv.isObject() || !interfaceID.isObject()) {
    XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
    return false;
  }

  // Call 'createInstance' on the 'classID' object to create the object.
  RootedValue instancev(cx);
  RootedObject classID(cx, &classIDv.toObject());
  if (!JS_CallFunctionName(cx, classID, "createInstance",
                           HandleValueArray(interfaceID), &instancev)) {
    return false;
  }
  if (!instancev.isObject()) {
    XPCThrower::Throw(NS_ERROR_FAILURE, cx);
    return false;
  }

  // Call the method 'initializer' on the instance, passing in our parameters.
  if (!initializer.isUndefined()) {
    RootedValue dummy(cx);
    RootedValue initfunc(cx);
    RootedId initid(cx);
    RootedObject instance(cx, &instancev.toObject());
    if (!JS_ValueToId(cx, initializer, &initid) ||
        !JS_GetPropertyById(cx, instance, initid, &initfunc) ||
        !JS_CallFunctionValue(cx, instance, initfunc, args, &dummy)) {
      return false;
    }
  }

  args.rval().set(instancev);
  return true;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::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_Constructor::Construct(nsIXPConnectWrappedNative* wrapper,
                                       JSContext* cx, JSObject* objArg,
                                       const CallArgs& args, bool* _retval) {
  RootedObject obj(cx, objArg);
  return CallOrConstruct(wrapper, cx, obj, args, _retval);
}

// static
nsresult nsXPCComponents_Constructor::CallOrConstruct(
    nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj,
    const CallArgs& args, bool* _retval) {
  // make sure we have at least one arg

  if (args.length() < 1) {
    return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);
  }

  // Fetch the property name ids, so we can look them up.
  XPCJSRuntime* runtime = XPCJSRuntime::Get();
  HandleId classIDProp = runtime->GetStringID(XPCJSContext::IDX_CLASS_ID);
  HandleId interfaceIDProp =
      runtime->GetStringID(XPCJSContext::IDX_INTERFACE_ID);
  HandleId initializerProp =
      runtime->GetStringID(XPCJSContext::IDX_INITIALIZER);

  // get the various other object pointers we need

  nsIXPConnect* xpc = nsIXPConnect::XPConnect();
  XPCWrappedNativeScope* scope = ObjectScope(obj);
  nsCOMPtr<nsIXPCComponents> comp;

  if (!xpc || !scope || !(comp = scope->GetComponents())) {
    return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
  }

  // Prevent non-chrome code from creating constructor objects.
  if (!nsContentUtils::IsCallerChrome()) {
    return ThrowAndFail(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, cx, _retval);
  }

  JSFunction* ctorfn = JS_NewFunction(cx, InnerConstructor, 0,
                                      JSFUN_CONSTRUCTOR, "XPCOM_Constructor");
  if (!ctorfn) {
    return ThrowAndFail(NS_ERROR_OUT_OF_MEMORY, cx, _retval);
  }

  JS::RootedObject ctor(cx, JS_GetFunctionObject(ctorfn));

  if (args.length() >= 3) {
    // args[2] is an initializer function or property name
    RootedString str(cx, ToString(cx, args[2]));
    if (!JS_DefinePropertyById(
            cx, ctor, initializerProp, str,
            JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
      return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval);
    }
  }

  RootedString ifaceName(cx);
  if (args.length() >= 2) {
    ifaceName = ToString(cx, args[1]);
  } else {
    ifaceName = JS_NewStringCopyZ(cx, "nsISupports");
  }

  if (!ifaceName) {
    return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval);
  }

  // a new scope to avoid warnings about shadowed names
  {
    nsCOMPtr<nsIXPCComponents_Interfaces> ifaces;
    RootedObject ifacesObj(cx);

    // we do the lookup by asking the Components.interfaces object
    // for the property with this name - i.e. we let its caching of these
    // nsIJSIID objects work for us.

    if (NS_FAILED(comp->GetInterfaces(getter_AddRefs(ifaces))) ||
        NS_FAILED(xpc->WrapNative(cx, obj, ifaces,
                                  NS_GET_IID(nsIXPCComponents_Interfaces),
                                  ifacesObj.address())) ||
        !ifacesObj) {
      return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
    }

    RootedId id(cx);
    if (!JS_StringToId(cx, ifaceName, &id)) {
      return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval);
    }

    RootedValue val(cx);
    if (!JS_GetPropertyById(cx, ifacesObj, id, &val) || val.isPrimitive()) {
      return ThrowAndFail(NS_ERROR_XPC_BAD_IID, cx, _retval);
    }

    if (!JS_DefinePropertyById(
            cx, ctor, interfaceIDProp, val,
            JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
      return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval);
    }
  }

  // a new scope to avoid warnings about shadowed names
  {
    // argv[0] is a contractid name string

    // we do the lookup by asking the Components.classes object
    // for the property with this name - i.e. we let its caching of these
    // nsIJSCID objects work for us.

    nsCOMPtr<nsIXPCComponents_Classes> classes;
    RootedObject classesObj(cx);

    if (NS_FAILED(comp->GetClasses(getter_AddRefs(classes))) ||
        NS_FAILED(xpc->WrapNative(cx, obj, classes,
                                  NS_GET_IID(nsIXPCComponents_Classes),
                                  classesObj.address())) ||
        !classesObj) {
      return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
    }

    RootedString str(cx, ToString(cx, args[0]));
    RootedId id(cx);
    if (!str || !JS_StringToId(cx, str, &id)) {
      return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval);
    }

    RootedValue val(cx);
    if (!JS_GetPropertyById(cx, classesObj, id, &val) || val.isPrimitive()) {
      return ThrowAndFail(NS_ERROR_XPC_BAD_CID, cx, _retval);
    }

    if (!JS_DefinePropertyById(
            cx, ctor, classIDProp, val,
            JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
      return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval);
    }
  }

  args.rval().setObject(*ctor);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Constructor::HasInstance(nsIXPConnectWrappedNative* wrapper,
                                         JSContext* cx, JSObject* obj,
                                         HandleValue val, bool* isa,
                                         bool* _retval) {
  *isa =
      val.isObject() && JS_IsNativeFunction(&val.toObject(), InnerConstructor);
  return NS_OK;
}

class nsXPCComponents_Utils final : public nsIXPCComponents_Utils,
                                    public nsIXPCScriptable {
 public:
  // all the interface method declarations...
  NS_DECL_ISUPPORTS
  NS_DECL_NSIXPCSCRIPTABLE
  NS_DECL_NSIXPCCOMPONENTS_UTILS

 public:
  nsXPCComponents_Utils() = default;

 private:
  virtual ~nsXPCComponents_Utils() = default;
  nsCOMPtr<nsIXPCComponents_utils_Sandbox> mSandbox;
};

NS_IMPL_ISUPPORTS(nsXPCComponents_Utils, nsIXPCComponents_Utils,
                  nsIXPCScriptable)

// The nsIXPCScriptable map declaration that will generate stubs for us...
#define XPC_MAP_CLASSNAME nsXPCComponents_Utils
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Utils"
#define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
nsXPCComponents_Utils::GetSandbox(nsIXPCComponents_utils_Sandbox** aSandbox) {
  NS_ENSURE_ARG_POINTER(aSandbox);
  if (!mSandbox) {
    mSandbox = NewSandboxConstructor();
  }

  nsCOMPtr<nsIXPCComponents_utils_Sandbox> rval = mSandbox;
  rval.forget(aSandbox);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::PrintStderr(const nsACString& message) {
  printf_stderr("%s", PromiseFlatUTF8String(message).get());
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ReportError(HandleValue error, HandleValue stack,
                                   JSContext* cx) {
  // This function shall never fail! Silently eat any failure conditions.

  nsCOMPtr<nsIConsoleService> console(
      do_GetService(NS_CONSOLESERVICE_CONTRACTID));
  if (!console) {
    return NS_OK;
  }

  nsGlobalWindowInner* win = CurrentWindowOrNull(cx);
  const uint64_t innerWindowID = win ? win->WindowID() : 0;

  Rooted<Maybe<Value>> exception(cx, Some(error));
  if (!innerWindowID) {
    // Leak mitigation: nsConsoleService::ClearMessagesForWindowID needs
    // a WindowID for cleanup and exception values could hold arbitrary
    // objects alive.
    exception = Nothing();
  }

  nsCOMPtr<nsIScriptError> scripterr;
  RootedObject errorObj(cx, error.isObject() ? &error.toObject() : nullptr);
  if (errorObj) {
    JS::RootedObject stackVal(cx);
    JS::RootedObject stackGlobal(cx);
    FindExceptionStackForConsoleReport(win, error, nullptr, &stackVal,
                                       &stackGlobal);
    if (stackVal) {
      scripterr = CreateScriptError(win, exception, stackVal, stackGlobal);
    }
  }

  nsCString fileName;
  uint32_t lineNo = 0;

  if (!scripterr) {
    RootedObject stackObj(cx);
    RootedObject stackGlobal(cx);
    if (stack.isObject()) {
      if (!JS::IsMaybeWrappedSavedFrame(&stack.toObject())) {
        return NS_ERROR_INVALID_ARG;
      }

      // |stack| might be a wrapper, but it must be same-compartment with
      // the current global.
      stackObj = &stack.toObject();
      stackGlobal = JS::CurrentGlobalOrNull(cx);
      js::AssertSameCompartment(stackObj, stackGlobal);

      JSPrincipals* principals =
          JS::GetRealmPrincipals(js::GetContextRealm(cx));

      if (GetSavedFrameLine(cx, principals, stackObj, &lineNo) !=
          SavedFrameResult::Ok) {
        JS_ClearPendingException(cx);
      }

      RootedString source(cx);
      nsAutoJSCString str;
      if (GetSavedFrameSource(cx, principals, stackObj, &source) ==
              SavedFrameResult::Ok &&
          str.init(cx, source)) {
        fileName = str;
      } else {
        JS_ClearPendingException(cx);
      }
    } else {
      nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();
      if (frame) {
        frame->GetFilename(cx, fileName);
        lineNo = frame->GetLineNumber(cx);
        JS::Rooted<JS::Value> stack(cx);
        nsresult rv = frame->GetNativeSavedFrame(&stack);
        if (NS_SUCCEEDED(rv) && stack.isObject()) {
          stackObj = &stack.toObject();
          MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
          stackGlobal = JS::GetNonCCWObjectGlobal(stackObj);
        }
      }
    }

    if (stackObj) {
      scripterr = CreateScriptError(win, exception, stackObj, stackGlobal);
    }
  }

  if (!scripterr) {
    scripterr = CreateScriptError(win, exception, nullptr, nullptr);
  }

  JSErrorReport* err = errorObj ? JS_ErrorFromException(cx, errorObj) : nullptr;
  if (err) {
    // It's a proper JS Error
    uint32_t flags = err->isWarning() ? nsIScriptError::warningFlag
                                      : nsIScriptError::errorFlag;
    nsresult rv = scripterr->InitWithWindowID(
        err->message() ? NS_ConvertUTF8toUTF16(err->message().c_str())
                       : EmptyString(),
        nsDependentCString(err->filename ? err->filename.c_str() : ""),
        err->lineno, err->column.oneOriginValue(), flags,
        "XPConnect JavaScript", innerWindowID, innerWindowID == 0);
    NS_ENSURE_SUCCESS(rv, NS_OK);

    console->LogMessage(scripterr);
    return NS_OK;
  }

  // It's not a JS Error object, so we synthesize as best we're able.
  RootedString msgstr(cx, ToString(cx, error));
  if (!msgstr) {
    return NS_OK;
  }

  nsAutoJSString msg;
  if (!msg.init(cx, msgstr)) {
    return NS_OK;
  }

  nsresult rv = scripterr->InitWithWindowID(msg, fileName, lineNo, 0, 0,
                                            "XPConnect JavaScript",
                                            innerWindowID, innerWindowID == 0);
  NS_ENSURE_SUCCESS(rv, NS_OK);

  console->LogMessage(scripterr);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::EvalInSandbox(
    const nsAString& source, HandleValue sandboxVal, HandleValue version,
    const nsACString& filenameArg, int32_t lineNumber,
    bool enforceFilenameRestrictions, JSContext* cx, uint8_t optionalArgc,
    MutableHandleValue retval) {
  RootedObject sandbox(cx);
  if (!JS_ValueToObject(cx, sandboxVal, &sandbox) || !sandbox) {
    return NS_ERROR_INVALID_ARG;
  }

  // Optional third argument: JS version, as a string, is unused.

  // Optional fourth and fifth arguments: filename and line number.
  int32_t lineNo = (optionalArgc >= 3) ? lineNumber : 1;
  nsCString filename;
  if (!filenameArg.IsVoid()) {
    filename.Assign(filenameArg);
  } else {
    // Get the current source info.
    if (nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack()) {
      frame->GetFilename(cx, filename);
      lineNo = frame->GetLineNumber(cx);
    }
  }
  enforceFilenameRestrictions =
      (optionalArgc >= 4) ? enforceFilenameRestrictions : true;

  return xpc::EvalInSandbox(cx, sandbox, source, filename, lineNo,
                            enforceFilenameRestrictions, retval);
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetUAWidgetScope(nsIPrincipal* principal, JSContext* cx,
                                        MutableHandleValue rval) {
  rval.set(UndefinedValue());

  JSObject* scope = xpc::GetUAWidgetScope(cx, principal);

  rval.set(JS::ObjectValue(*scope));

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetSandboxMetadata(HandleValue sandboxVal, JSContext* cx,
                                          MutableHandleValue rval) {
  if (!sandboxVal.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }

  RootedObject sandbox(cx, &sandboxVal.toObject());
  // We only care about sandboxes here, so CheckedUnwrapStatic is fine.
  sandbox = js::CheckedUnwrapStatic(sandbox);
  if (!sandbox || !xpc::IsSandbox(sandbox)) {
    return NS_ERROR_INVALID_ARG;
  }

  return xpc::GetSandboxMetadata(cx, sandbox, rval);
}

NS_IMETHODIMP
nsXPCComponents_Utils::SetSandboxMetadata(HandleValue sandboxVal,
                                          HandleValue metadataVal,
                                          JSContext* cx) {
  if (!sandboxVal.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }

  RootedObject sandbox(cx, &sandboxVal.toObject());
  // We only care about sandboxes here, so CheckedUnwrapStatic is fine.
  sandbox = js::CheckedUnwrapStatic(sandbox);
  if (!sandbox || !xpc::IsSandbox(sandbox)) {
    return NS_ERROR_INVALID_ARG;
  }

  nsresult rv = xpc::SetSandboxMetadata(cx, sandbox, metadataVal);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsESModuleLoaded(const nsACString& aResourceURI,
                                        bool* retval) {
  RefPtr moduleloader = mozJSModuleLoader::Get();
  MOZ_ASSERT(moduleloader);
  return moduleloader->IsESModuleLoaded(aResourceURI, retval);
}

NS_IMETHODIMP
nsXPCComponents_Utils::ImportGlobalProperties(HandleValue aPropertyList,
                                              JSContext* cx) {
  // Ensure we're working in the scripted caller's realm. This is not guaranteed
  // to be the current realm because we switch realms when calling cross-realm
  // functions.
  RootedObject global(cx, JS::GetScriptedCallerGlobal(cx));
  MOZ_ASSERT(global);
  js::AssertSameCompartment(cx, global);
  JSAutoRealm ar(cx, global);

  // Don't allow doing this if the global is a Window.
  nsGlobalWindowInner* win;
  if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, win))) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  GlobalProperties options;
  NS_ENSURE_TRUE(aPropertyList.isObject(), NS_ERROR_INVALID_ARG);

  RootedObject propertyList(cx, &aPropertyList.toObject());
  bool isArray;
  if (NS_WARN_IF(!JS::IsArrayObject(cx, propertyList, &isArray))) {
    return NS_ERROR_FAILURE;
  }
  if (NS_WARN_IF(!isArray)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (!options.Parse(cx, propertyList) ||
      !options.DefineInXPCComponents(cx, global)) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetWeakReference(HandleValue object, JSContext* cx,
                                        xpcIJSWeakReference** _retval) {
  RefPtr<xpcJSWeakReference> ref = new xpcJSWeakReference();
  nsresult rv = ref->Init(cx, object);
  NS_ENSURE_SUCCESS(rv, rv);
  ref.forget(_retval);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ForceGC(JSContext* aCx) {
  PrepareForFullGC(aCx);
  NonIncrementalGC(aCx, GCOptions::Normal, GCReason::COMPONENT_UTILS);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ForceCC(nsICycleCollectorListener* listener) {
  nsJSContext::CycleCollectNow(CCReason::API, listener);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateCCLogger(nsICycleCollectorListener** aListener) {
  NS_ENSURE_ARG_POINTER(aListener);
  nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
  logger.forget(aListener);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::FinishCC() {
  nsCycleCollector_finishAnyCurrentCollection();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CcSlice(int64_t budget) {
  nsJSContext::RunCycleCollectorWorkSlice(budget);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetMaxCCSliceTimeSinceClear(int32_t* out) {
  *out = nsJSContext::GetMaxCCSliceTimeSinceClear();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ClearMaxCCTime() {
  nsJSContext::ClearMaxCCSliceTime();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ForceShrinkingGC(JSContext* aCx) {
  PrepareForFullGC(aCx);
  NonIncrementalGC(aCx, GCOptions::Shrink, GCReason::COMPONENT_UTILS);
  return NS_OK;
}

class PreciseGCRunnable : public Runnable {
 public:
  PreciseGCRunnable(nsIScheduledGCCallback* aCallback, bool aShrinking)
      : mozilla::Runnable("PreciseGCRunnable"),
        mCallback(aCallback),
        mShrinking(aShrinking) {}

  NS_IMETHOD Run() override {
    nsJSContext::GarbageCollectNow(
        GCReason::COMPONENT_UTILS,
        mShrinking ? nsJSContext::ShrinkingGC : nsJSContext::NonShrinkingGC);

    mCallback->Callback();
    return NS_OK;
  }

 private:
  nsCOMPtr<nsIScheduledGCCallback> mCallback;
  bool mShrinking;
};

NS_IMETHODIMP
nsXPCComponents_Utils::SchedulePreciseGC(nsIScheduledGCCallback* aCallback) {
  RefPtr<PreciseGCRunnable> event = new PreciseGCRunnable(aCallback, false);
  return NS_DispatchToMainThread(event);
}

NS_IMETHODIMP
nsXPCComponents_Utils::SchedulePreciseShrinkingGC(
    nsIScheduledGCCallback* aCallback) {
  RefPtr<PreciseGCRunnable> event = new PreciseGCRunnable(aCallback, true);
  return NS_DispatchToMainThread(event);
}

NS_IMETHODIMP
nsXPCComponents_Utils::UnlinkGhostWindows() {
#ifdef DEBUG
  nsWindowMemoryReporter::UnlinkGhostWindows();

  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
    if (obsvc) {
      obsvc->NotifyObservers(nullptr, "child-ghost-request", nullptr);
    }
  }

  return NS_OK;
#else
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

#ifdef NS_FREE_PERMANENT_DATA
struct IntentionallyLeakedObject {
  MOZ_COUNTED_DEFAULT_CTOR(IntentionallyLeakedObject)

  MOZ_COUNTED_DTOR(IntentionallyLeakedObject)
};
#endif

NS_IMETHODIMP
nsXPCComponents_Utils::IntentionallyLeak() {
#ifdef NS_FREE_PERMANENT_DATA
  Unused << new IntentionallyLeakedObject();
  return NS_OK;
#else
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetJSTestingFunctions(JSContext* cx,
                                             MutableHandleValue retval) {
  JSObject* obj = js::GetTestingFunctions(cx);
  if (!obj) {
    return NS_ERROR_XPC_JAVASCRIPT_ERROR;
  }
  retval.setObject(*obj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetFunctionSourceLocation(HandleValue funcValue,
                                                 JSContext* cx,
                                                 MutableHandleValue retval) {
  NS_ENSURE_TRUE(funcValue.isObject(), NS_ERROR_INVALID_ARG);

  nsAutoString filename;
  uint32_t lineNumber;
  {
    RootedObject funcObj(cx, UncheckedUnwrap(&funcValue.toObject()));
    JSAutoRealm ar(cx, funcObj);

    Rooted<JSFunction*> func(cx, JS_GetObjectFunction(funcObj));
    NS_ENSURE_TRUE(func, NS_ERROR_INVALID_ARG);

    RootedScript script(cx, JS_GetFunctionScript(cx, func));
    NS_ENSURE_TRUE(script, NS_ERROR_FAILURE);

    AppendUTF8toUTF16(nsDependentCString(JS_GetScriptFilename(script)),
                      filename);
    lineNumber = JS_GetScriptBaseLineNumber(cx, script) + 1;
  }

  RootedObject res(cx, JS_NewPlainObject(cx));
  NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY);

  RootedValue filenameVal(cx);
  if (!xpc::NonVoidStringToJsval(cx, filename, &filenameVal) ||
      !JS_DefineProperty(cx, res, "filename", filenameVal, JSPROP_ENUMERATE)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (!JS_DefineProperty(cx, res, "lineNumber", lineNumber, JSPROP_ENUMERATE)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  retval.setObject(*res);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function,
                                                  nsIStackFrame* stack,
                                                  const nsAString& asyncCause,
                                                  JSContext* cx,
                                                  MutableHandleValue retval) {
  nsresult rv;

  if (!stack || asyncCause.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  JS::Rooted<JS::Value> asyncStack(cx);
  rv = stack->GetNativeSavedFrame(&asyncStack);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!asyncStack.isObject()) {
    JS_ReportErrorASCII(cx, "Must use a native JavaScript stack frame");
    return NS_ERROR_INVALID_ARG;
  }

  JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject());

  NS_ConvertUTF16toUTF8 utf8Cause(asyncCause);
  JS::AutoSetAsyncStackForNewCalls sas(
      cx, asyncStackObj, utf8Cause.get(),
      JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);

  if (!JS_CallFunctionValue(cx, nullptr, function,
                            JS::HandleValueArray::empty(), retval)) {
    return NS_ERROR_XPC_JAVASCRIPT_ERROR;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetGlobalForObject(HandleValue object, JSContext* cx,
                                          MutableHandleValue retval) {
  // First argument must be an object.
  if (object.isPrimitive()) {
    return NS_ERROR_XPC_BAD_CONVERT_JS;
  }

  // When getting the global for a cross-compartment wrapper, we really want
  // a wrapper for the foreign global. So we need to unwrap before getting the
  // global and then wrap the result.
  Rooted<JSObject*> obj(cx, &object.toObject());
  obj = JS::GetNonCCWObjectGlobal(js::UncheckedUnwrap(obj));

  if (!JS_WrapObject(cx, &obj)) {
    return NS_ERROR_FAILURE;
  }

  // Get the WindowProxy if necessary.
  obj = js::ToWindowProxyIfWindow(obj);

  retval.setObject(*obj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsProxy(HandleValue vobj, JSContext* cx, bool* rval) {
  if (!vobj.isObject()) {
    *rval = false;
    return NS_OK;
  }

  RootedObject obj(cx, &vobj.toObject());
  // We need to do a dynamic unwrap, because we apparently want to treat
  // "failure to unwrap" differently from "not a proxy" (throw for the former,
  // return false for the latter).
  obj = js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
  NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);

  *rval = js::IsScriptedProxy(obj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ExportFunction(HandleValue vfunction, HandleValue vscope,
                                      HandleValue voptions, JSContext* cx,
                                      MutableHandleValue rval) {
  if (!xpc::ExportFunction(cx, vfunction, vscope, voptions, rval)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateObjectIn(HandleValue vobj, HandleValue voptions,
                                      JSContext* cx, MutableHandleValue rval) {
  RootedObject optionsObject(
      cx, voptions.isObject() ? &voptions.toObject() : nullptr);
  CreateObjectInOptions options(cx, optionsObject);
  if (voptions.isObject() && !options.Parse()) {
    return NS_ERROR_FAILURE;
  }

  if (!xpc::CreateObjectIn(cx, vobj, options, rval)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::MakeObjectPropsNormal(HandleValue vobj, JSContext* cx) {
  if (!cx) {
    return NS_ERROR_FAILURE;
  }

  // first argument must be an object
  if (vobj.isPrimitive()) {
    return NS_ERROR_XPC_BAD_CONVERT_JS;
  }

  RootedObject obj(cx, js::UncheckedUnwrap(&vobj.toObject()));
  JSAutoRealm ar(cx, obj);
  Rooted<IdVector> ida(cx, IdVector(cx));
  if (!JS_Enumerate(cx, obj, &ida)) {
    return NS_ERROR_FAILURE;
  }

  RootedId id(cx);
  RootedValue v(cx);
  for (size_t i = 0; i < ida.length(); ++i) {
    id = ida[i];

    if (!JS_GetPropertyById(cx, obj, id, &v)) {
      return NS_ERROR_FAILURE;
    }

    if (v.isPrimitive()) {
      continue;
    }

    RootedObject propobj(cx, &v.toObject());
    // TODO Deal with non-functions.
    if (!js::IsWrapper(propobj) || !JS::IsCallable(propobj)) {
      continue;
    }

    FunctionForwarderOptions forwarderOptions;
    if (!NewFunctionForwarder(cx, id, propobj, forwarderOptions, &v) ||
        !JS_SetPropertyById(cx, obj, id, v))
      return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsDeadWrapper(HandleValue obj, bool* out) {
  *out = false;
  if (obj.isPrimitive()) {
    return NS_ERROR_INVALID_ARG;
  }

  // We should never have cross-compartment wrappers for dead wrappers.
  MOZ_ASSERT_IF(js::IsCrossCompartmentWrapper(&obj.toObject()),
                !JS_IsDeadWrapper(js::UncheckedUnwrap(&obj.toObject())));

  *out = JS_IsDeadWrapper(&obj.toObject());
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsRemoteProxy(HandleValue val, bool* out) {
  if (val.isObject()) {
    *out = dom::IsRemoteObjectProxy(UncheckedUnwrap(&val.toObject()));
    ;
  } else {
    *out = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::RecomputeWrappers(HandleValue vobj, JSContext* cx) {
  // Determine the compartment of the given object, if any.
  JS::Compartment* c =
      vobj.isObject()
          ? JS::GetCompartment(js::UncheckedUnwrap(&vobj.toObject()))
          : nullptr;

  // If no compartment was given, recompute all.
  if (!c) {
    js::RecomputeWrappers(cx, js::AllCompartments(), js::AllCompartments());
    // Otherwise, recompute wrappers for the given compartment.
  } else {
    js::RecomputeWrappers(cx, js::SingleCompartment(c),
                          js::AllCompartments()) &&
        js::RecomputeWrappers(cx, js::AllCompartments(),
                              js::SingleCompartment(c));
  }

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::SetWantXrays(HandleValue vscope, JSContext* cx) {
  if (!vscope.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }
  JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject());
  MOZ_RELEASE_ASSERT(!AccessCheck::isChrome(scopeObj),
                     "Don't call setWantXrays on system-principal scopes");
  JS::Compartment* compartment = JS::GetCompartment(scopeObj);
  CompartmentPrivate::Get(scopeObj)->wantXrays = true;
  bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment),
                                  js::AllCompartments());
  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::Dispatch(HandleValue runnableArg, HandleValue scope,
                                JSContext* cx) {
  RootedValue runnable(cx, runnableArg);
  // Enter the given realm, if any, and rewrap runnable.
  Maybe<JSAutoRealm> ar;
  if (scope.isObject()) {
    JSObject* scopeObj = js::UncheckedUnwrap(&scope.toObject());
    if (!scopeObj) {
      return NS_ERROR_FAILURE;
    }
    ar.emplace(cx, scopeObj);
    if (!JS_WrapValue(cx, &runnable)) {
      return NS_ERROR_FAILURE;
    }
  }

  // Get an XPCWrappedJS for |runnable|.
  if (!runnable.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }

  RootedObject runnableObj(cx, &runnable.toObject());
  nsCOMPtr<nsIRunnable> run;
  nsresult rv = nsXPConnect::XPConnect()->WrapJS(
      cx, runnableObj, NS_GET_IID(nsIRunnable), getter_AddRefs(run));
  NS_ENSURE_SUCCESS(rv, rv);
  MOZ_ASSERT(run);

  // Dispatch.
  return NS_DispatchToMainThread(run);
}

NS_IMETHODIMP
nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx) {
#ifdef JS_GC_ZEAL
  JS::SetGCZeal(cx, uint8_t(aValue), JS::BrowserDefaultGCZealFrequency);
#endif
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetIsInAutomation(bool* aResult) {
  NS_ENSURE_ARG_POINTER(aResult);

  *aResult = xpc::IsInAutomation();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ExitIfInAutomation() {
  NS_ENSURE_TRUE(xpc::IsInAutomation(), NS_ERROR_FAILURE);

  profiler_shutdown(IsFastShutdown::Yes);

  mozilla::AppShutdown::DoImmediateExit();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CrashIfNotInAutomation() {
  xpc::CrashIfNotInAutomation();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) {
  AUTO_PROFILER_LABEL("nsXPCComponents_Utils::NukeSandbox", OTHER);
  NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG);
  JSObject* wrapper = &obj.toObject();
  NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG);
  RootedObject sb(cx, UncheckedUnwrap(wrapper));
  NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);

  xpc::NukeAllWrappersForRealm(cx, GetNonCCWObjectRealm(sb));

  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg,
                                            JSContext* cx) {
  NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
  RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(),
                                          /* stopAtWindowProxy = */ false));
  NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
  if (xpc::GetObjectPrincipal(global)->IsSystemPrincipal()) {
    JS_ReportErrorASCII(cx, "Script may not be disabled for system globals");
    return NS_ERROR_FAILURE;
  }
  Scriptability::Get(global).Block();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::UnblockScriptForGlobal(HandleValue globalArg,
                                              JSContext* cx) {
  NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
  RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(),
                                          /* stopAtWindowProxy = */ false));
  NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
  if (xpc::GetObjectPrincipal(global)->IsSystemPrincipal()) {
    JS_ReportErrorASCII(cx, "Script may not be disabled for system globals");
    return NS_ERROR_FAILURE;
  }
  Scriptability::Get(global).Unblock();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsOpaqueWrapper(HandleValue obj, bool* aRetval) {
  *aRetval =
      obj.isObject() && xpc::WrapperFactory::IsOpaqueWrapper(&obj.toObject());
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::IsXrayWrapper(HandleValue obj, bool* aRetval) {
  *aRetval =
      obj.isObject() && xpc::WrapperFactory::IsXrayWrapper(&obj.toObject());
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::WaiveXrays(HandleValue aVal, JSContext* aCx,
                                  MutableHandleValue aRetval) {
  RootedValue value(aCx, aVal);
  if (!xpc::WrapperFactory::WaiveXrayAndWrap(aCx, &value)) {
    return NS_ERROR_FAILURE;
  }
  aRetval.set(value);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::UnwaiveXrays(HandleValue aVal, JSContext* aCx,
                                    MutableHandleValue aRetval) {
  if (!aVal.isObject()) {
    aRetval.set(aVal);
    return NS_OK;
  }

  RootedObject obj(aCx, js::UncheckedUnwrap(&aVal.toObject()));
  if (!JS_WrapObject(aCx, &obj)) {
    return NS_ERROR_FAILURE;
  }
  aRetval.setObject(*obj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetClassName(HandleValue aObj, bool aUnwrap,
                                    JSContext* aCx, char** aRv) {
  if (!aObj.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }
  RootedObject obj(aCx, &aObj.toObject());
  if (aUnwrap) {
    obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
  }
  *aRv = NS_xstrdup(JS::GetClass(obj)->name);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetIncumbentGlobal(HandleValue aCallback, JSContext* aCx,
                                          MutableHandleValue aOut) {
  nsCOMPtr<nsIGlobalObject> global = mozilla::dom::GetIncumbentGlobal();
  RootedValue globalVal(aCx);

  if (!global) {
    globalVal = NullValue();
  } else {
    // Note: We rely on the wrap call for outerization.
    globalVal = ObjectValue(*global->GetGlobalJSObject());
    if (!JS_WrapValue(aCx, &globalVal)) {
      return NS_ERROR_FAILURE;
    }
  }

  // Invoke the callback, if passed.
  if (aCallback.isObject()) {
    RootedValue ignored(aCx);
    if (!JS_CallFunctionValue(aCx, nullptr, aCallback,
                              JS::HandleValueArray(globalVal), &ignored)) {
      return NS_ERROR_FAILURE;
    }
  }

  aOut.set(globalVal);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetDebugName(HandleValue aObj, JSContext* aCx,
                                    nsACString& aOut) {
  if (!aObj.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }

  RootedObject obj(aCx, &aObj.toObject());
  aOut = xpc::GetFunctionName(aCx, obj);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory,
                                            PRTime* aOut) {
  WatchdogTimestampCategory category;
  if (aCategory.EqualsLiteral("ContextStateChange")) {
    category = TimestampContextStateChange;
  } else if (aCategory.EqualsLiteral("WatchdogWakeup")) {
    category = TimestampWatchdogWakeup;
  } else if (aCategory.EqualsLiteral("WatchdogHibernateStart")) {
    category = TimestampWatchdogHibernateStart;
  } else if (aCategory.EqualsLiteral("WatchdogHibernateStop")) {
    category = TimestampWatchdogHibernateStop;
  } else {
    return NS_ERROR_INVALID_ARG;
  }
  *aOut = XPCJSContext::Get()->GetWatchdogTimestamp(category);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetJSEngineTelemetryValue(JSContext* cx,
                                                 MutableHandleValue rval) {
  RootedObject obj(cx, JS_NewPlainObject(cx));
  if (!obj) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // No JS engine telemetry in use at the moment.

  rval.setObject(*obj);
  return NS_OK;
}

bool xpc::CloneInto(JSContext* aCx, HandleValue aValue, HandleValue aScope,
                    HandleValue aOptions, MutableHandleValue aCloned) {
  if (!aScope.isObject()) {
    return false;
  }

  RootedObject scope(aCx, &aScope.toObject());
  // The scope could be a Window, so we need to CheckedUnwrapDynamic.
  scope = js::CheckedUnwrapDynamic(scope, aCx);
  if (!scope) {
    JS_ReportErrorASCII(aCx, "Permission denied to clone object into scope");
    return false;
  }

  if (!aOptions.isUndefined() && !aOptions.isObject()) {
    JS_ReportErrorASCII(aCx, "Invalid argument");
    return false;
  }

  RootedObject optionsObject(
      aCx, aOptions.isObject() ? &aOptions.toObject() : nullptr);
  StackScopedCloneOptions options(aCx, optionsObject);
  if (aOptions.isObject() && !options.Parse()) {
    return false;
  }

  js::AssertSameCompartment(aCx, aValue);
  RootedObject sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));

  {
    JSAutoRealm ar(aCx, scope);
    aCloned.set(aValue);
    if (!StackScopedClone(aCx, options, sourceScope, aCloned)) {
      return false;
    }
  }

  return JS_WrapValue(aCx, aCloned);
}

NS_IMETHODIMP
nsXPCComponents_Utils::CloneInto(HandleValue aValue, HandleValue aScope,
                                 HandleValue aOptions, JSContext* aCx,
                                 MutableHandleValue aCloned) {
  return xpc::CloneInto(aCx, aValue, aScope, aOptions, aCloned)
             ? NS_OK
             : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetWebIDLCallerPrincipal(nsIPrincipal** aResult) {
  // This API may only be when the Entry Settings Object corresponds to a
  // JS-implemented WebIDL call. In all other cases, the value will be null,
  // and we throw.
  nsCOMPtr<nsIPrincipal> callerPrin = mozilla::dom::GetWebIDLCallerPrincipal();
  if (!callerPrin) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  callerPrin.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetObjectPrincipal(HandleValue val, JSContext* cx,
                                          nsIPrincipal** result) {
  if (!val.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }
  RootedObject obj(cx, &val.toObject());
  // We need to be able to unwrap to WindowProxy or Location here, so
  // use CheckedUnwrapDynamic.
  obj = js::CheckedUnwrapDynamic(obj, cx);
  MOZ_ASSERT(obj);

  nsCOMPtr<nsIPrincipal> prin = nsContentUtils::ObjectPrincipal(obj);
  prin.forget(result);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetRealmLocation(HandleValue val, JSContext* cx,
                                        nsACString& result) {
  if (!val.isObject()) {
    return NS_ERROR_INVALID_ARG;
  }
  RootedObject obj(cx, &val.toObject());
  // We need to be able to unwrap to WindowProxy or Location here, so
  // use CheckedUnwrapDynamic.
  obj = js::CheckedUnwrapDynamic(obj, cx);
  MOZ_ASSERT(obj);

  result = xpc::RealmPrivate::Get(obj)->GetLocation();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ReadUTF8File(nsIFile* aFile, nsACString& aResult) {
  NS_ENSURE_TRUE(aFile, NS_ERROR_INVALID_ARG);

  MOZ_TRY_VAR(aResult, URLPreloader::ReadFile(aFile));
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::ReadUTF8URI(nsIURI* aURI, nsACString& aResult) {
  NS_ENSURE_TRUE(aURI, NS_ERROR_INVALID_ARG);

  MOZ_TRY_VAR(aResult, URLPreloader::ReadURI(aURI));
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::Now(double* aRetval) {
  TimeStamp start = TimeStamp::ProcessCreation();
  *aRetval = (TimeStamp::Now() - start).ToMilliseconds();
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateSpellChecker(nsIEditorSpellCheck** aSpellChecker) {
  NS_ENSURE_ARG_POINTER(aSpellChecker);
  nsCOMPtr<nsIEditorSpellCheck> spellChecker = new mozilla::EditorSpellCheck();
  spellChecker.forget(aSpellChecker);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateCommandLine(const nsTArray<nsCString>& aArgs,
                                         nsIFile* aWorkingDir, uint32_t aState,
                                         nsISupports** aCommandLine) {
  NS_ENSURE_ARG_MAX(aState, nsICommandLine::STATE_REMOTE_EXPLICIT);
  NS_ENSURE_ARG_POINTER(aCommandLine);

  nsCOMPtr<nsISupports> commandLine = new nsCommandLine();
  nsCOMPtr<nsICommandLineRunner> runner = do_QueryInterface(commandLine);

  nsTArray<const char*> fakeArgv(aArgs.Length() + 2);

  // Prepend a dummy argument for the program name, which will be ignored.
  fakeArgv.AppendElement(nullptr);
  for (const nsCString& arg : aArgs) {
    fakeArgv.AppendElement(arg.get());
  }
  // Append a null terminator.
  fakeArgv.AppendElement(nullptr);

  nsresult rv = runner->Init(fakeArgv.Length() - 1, fakeArgv.Elements(),
                             aWorkingDir, aState);
  NS_ENSURE_SUCCESS(rv, rv);

  commandLine.forget(aCommandLine);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateCommandParams(nsICommandParams** aCommandParams) {
  NS_ENSURE_ARG_POINTER(aCommandParams);
  nsCOMPtr<nsICommandParams> commandParams = new nsCommandParams();
  commandParams.forget(aCommandParams);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateLoadContext(nsILoadContext** aLoadContext) {
  NS_ENSURE_ARG_POINTER(aLoadContext);
  nsCOMPtr<nsILoadContext> loadContext = ::CreateLoadContext();
  loadContext.forget(aLoadContext);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreatePrivateLoadContext(nsILoadContext** aLoadContext) {
  NS_ENSURE_ARG_POINTER(aLoadContext);
  nsCOMPtr<nsILoadContext> loadContext = ::CreatePrivateLoadContext();
  loadContext.forget(aLoadContext);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreatePersistentProperties(
    nsIPersistentProperties** aPersistentProperties) {
  NS_ENSURE_ARG_POINTER(aPersistentProperties);
  nsCOMPtr<nsIPersistentProperties> props = new nsPersistentProperties();
  props.forget(aPersistentProperties);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateDocumentEncoder(
    const char* aContentType, nsIDocumentEncoder** aDocumentEncoder) {
  NS_ENSURE_ARG_POINTER(aDocumentEncoder);
  nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder(aContentType);
  encoder.forget(aDocumentEncoder);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::CreateHTMLCopyEncoder(
    nsIDocumentEncoder** aDocumentEncoder) {
  NS_ENSURE_ARG_POINTER(aDocumentEncoder);
  nsCOMPtr<nsIDocumentEncoder> encoder = do_createHTMLCopyEncoder();
  encoder.forget(aDocumentEncoder);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetLoadedESModules(
    nsTArray<nsCString>& aLoadedESModules) {
  return mozJSModuleLoader::Get()->GetLoadedESModules(aLoadedESModules);
}

NS_IMETHODIMP
nsXPCComponents_Utils::GetModuleImportStack(const nsACString& aLocation,
                                            nsACString& aRetval) {
  nsresult rv =
      mozJSModuleLoader::Get()->GetModuleImportStack(aLocation, aRetval);
  // Fallback the query to the DevTools loader if not found in the shared loader
  if (rv == NS_ERROR_FAILURE && mozJSModuleLoader::GetDevToolsLoader()) {
    return mozJSModuleLoader::GetDevToolsLoader()->GetModuleImportStack(
        aLocation, aRetval);
  }
  return rv;
}

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

nsXPCComponents::nsXPCComponents(XPCWrappedNativeScope* aScope)
    : mScope(aScope) {
  MOZ_ASSERT(aScope, "aScope must not be null");
}

nsXPCComponents::~nsXPCComponents() = default;

void nsXPCComponents::ClearMembers() {
  mInterfaces = nullptr;
  mResults = nullptr;
  mClasses = nullptr;
  mID = nullptr;
  mException = nullptr;
  mConstructor = nullptr;
  mUtils = nullptr;
}

/*******************************************/
#define XPC_IMPL_GET_OBJ_METHOD(_class, _n)                      \
  NS_IMETHODIMP _class::Get##_n(nsIXPCComponents_##_n** a##_n) { \
    NS_ENSURE_ARG_POINTER(a##_n);                                \
    if (!m##_n) m##_n = new nsXPCComponents_##_n();              \
    RefPtr<nsXPCComponents_##_n> ret = m##_n;                    \
    ret.forget(a##_n);                                           \
    return NS_OK;                                                \
  }

XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Interfaces)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Classes)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Results)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, ID)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Exception)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Constructor)
XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Utils)

#undef XPC_IMPL_GET_OBJ_METHOD
/*******************************************/

NS_IMETHODIMP
nsXPCComponents::IsSuccessCode(nsresult result, bool* out) {
  *out = NS_SUCCEEDED(result);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents::GetStack(nsIStackFrame** aStack) {
  nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();
  frame.forget(aStack);
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents::GetManager(nsIComponentManager** aManager) {
  MOZ_ASSERT(aManager, "bad param");
  return NS_GetComponentManager(aManager);
}

NS_IMETHODIMP
nsXPCComponents::GetReturnCode(JSContext* aCx, MutableHandleValue aOut) {
  nsresult res = XPCJSContext::Get()->GetPendingResult();
  aOut.setNumber(static_cast<uint32_t>(res));
  return NS_OK;
}

NS_IMETHODIMP
nsXPCComponents::SetReturnCode(JSContext* aCx, HandleValue aCode) {
  nsresult rv;
  if (!ToUint32(aCx, aCode, (uint32_t*)&rv)) {
    return NS_ERROR_FAILURE;
  }
  XPCJSContext::Get()->SetPendingResult(rv);
  return NS_OK;
}

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

class ComponentsSH : public nsIXPCScriptable {
 public:
  explicit constexpr ComponentsSH(unsigned dummy) {}

  // We don't actually inherit any ref counting infrastructure, but we don't
  // need an nsAutoRefCnt member, so the _INHERITED macro is a hack to avoid
  // having one.
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIXPCSCRIPTABLE
  static nsresult Get(nsIXPCScriptable** helper) {
    *helper = &singleton;
    return NS_OK;
  }

 private:
  static ComponentsSH singleton;
};

ComponentsSH ComponentsSH::singleton(0);

// Singleton refcounting.
NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::AddRef(void) { return 1; }
NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::Release(void) {
  return 1;
}

NS_IMPL_QUERY_INTERFACE(ComponentsSH, nsIXPCScriptable)

#define NSXPCCOMPONENTS_CID \
  {0x3649f405, 0xf0ec, 0x4c28, {0xae, 0xb0, 0xaf, 0x9a, 0x51, 0xe4, 0x4c, 0x81}}

NS_IMPL_CLASSINFO(nsXPCComponents, &ComponentsSH::Get, 0, NSXPCCOMPONENTS_CID)
NS_IMPL_ISUPPORTS_CI(nsXPCComponents, nsIXPCComponents)

// The nsIXPCScriptable map declaration that will generate stubs for us
#define XPC_MAP_CLASSNAME ComponentsSH
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents"
#define XPC_MAP_FLAGS XPC_SCRIPTABLE_WANT_PRECREATE
#include "xpc_map_end.h" /* This will #undef the above */

NS_IMETHODIMP
ComponentsSH::PreCreate(nsISupports* nativeObj, JSContext* cx,
                        JSObject* globalObj, JSObject** parentObj) {
  nsXPCComponents* self = static_cast<nsXPCComponents*>(nativeObj);
  // this should never happen
  if (!self->GetScope()) {
    NS_WARNING(
        "mScope must not be null when nsXPCComponents::PreCreate is called");
    return NS_ERROR_FAILURE;
  }
  *parentObj = self->GetScope()->GetGlobalForWrappedNatives();
  return NS_OK;
}
