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

#include "debugger/Object-inl.h"

#include "mozilla/Maybe.h"   // for Maybe, Nothing, Some
#include "mozilla/Range.h"   // for Range
#include "mozilla/Result.h"  // for Result
#include "mozilla/Vector.h"  // for Vector

#include <algorithm>
#include <string.h>     // for size_t, strlen
#include <type_traits>  // for remove_reference<>::type
#include <utility>      // for move

#include "jsapi.h"  // for CallArgs, RootedObject, Rooted

#include "builtin/Array.h"       // for NewDenseCopiedArray
#include "builtin/Promise.h"     // for PromiseReactionRecordBuilder
#include "debugger/Debugger.h"   // for Completion, Debugger
#include "debugger/Frame.h"      // for DebuggerFrame
#include "debugger/NoExecute.h"  // for LeaveDebuggeeNoExecute
#include "debugger/Script.h"     // for DebuggerScript
#include "debugger/Source.h"     // for DebuggerSource
#include "gc/Tracer.h"        // for TraceManuallyBarrieredCrossCompartmentEdge
#include "js/ColumnNumber.h"  // JS::ColumnNumberOneOrigin
#include "js/CompilationAndEvaluation.h"  //  for Compile
#include "js/Conversions.h"               // for ToObject
#include "js/experimental/JitInfo.h"      // for JSJitInfo
#include "js/friend/ErrorMessages.h"      // for GetErrorMessage, JSMSG_*
#include "js/friend/WindowProxy.h"  // for IsWindow, IsWindowProxy, ToWindowIfWindowProxy
#include "js/HeapAPI.h"             // for IsInsideNursery
#include "js/Promise.h"             // for PromiseState
#include "js/PropertyAndElement.h"       // for JS_GetProperty
#include "js/Proxy.h"                    // for PropertyDescriptor
#include "js/SourceText.h"               // for SourceText
#include "js/StableStringChars.h"        // for AutoStableStringChars
#include "js/String.h"                   // for JS::StringHasLatin1Chars
#include "proxy/ScriptedProxyHandler.h"  // for ScriptedProxyHandler
#include "vm/ArgumentsObject.h"          // for ARGS_LENGTH_MAX
#include "vm/ArrayObject.h"              // for ArrayObject
#include "vm/AsyncFunction.h"            // for AsyncGeneratorObject
#include "vm/AsyncIteration.h"           // for AsyncFunctionGeneratorObject
#include "vm/BoundFunctionObject.h"      // for BoundFunctionObject
#include "vm/BytecodeUtil.h"             // for JSDVG_SEARCH_STACK
#include "vm/Compartment.h"              // for Compartment
#include "vm/EnvironmentObject.h"        // for GetDebugEnvironmentForFunction
#include "vm/ErrorObject.h"              // for JSObject::is, ErrorObject
#include "vm/GeneratorObject.h"          // for AbstractGeneratorObject
#include "vm/GlobalObject.h"             // for JSObject::is, GlobalObject
#include "vm/Interpreter.h"              // for Call
#include "vm/JSAtomUtils.h"              // for Atomize, AtomizeString
#include "vm/JSContext.h"                // for JSContext, ReportValueError
#include "vm/JSFunction.h"               // for JSFunction
#include "vm/JSObject.h"                 // for GenericObject, NewObjectKind
#include "vm/JSScript.h"                 // for JSScript
#include "vm/NativeObject.h"             // for NativeObject, JSObject::is
#include "vm/ObjectOperations.h"         // for DefineProperty
#include "vm/PlainObject.h"              // for js::PlainObject
#include "vm/PromiseObject.h"            // for js::PromiseObject
#include "vm/Realm.h"                    // for AutoRealm, ErrorCopier, Realm
#include "vm/Runtime.h"                  // for JSAtomState
#include "vm/SavedFrame.h"               // for SavedFrame
#include "vm/Scope.h"                    // for PositionalFormalParameterIter
#include "vm/SelfHosting.h"              // for GetClonedSelfHostedFunctionName
#include "vm/Shape.h"                    // for Shape
#include "vm/Stack.h"                    // for InvokeArgs
#include "vm/StringType.h"               // for JSAtom, PropertyName
#include "vm/WrapperObject.h"            // for JSObject::is, WrapperObject

#include "gc/StableCellHasher-inl.h"
#include "vm/Compartment-inl.h"  // for Compartment::wrap
#include "vm/JSObject-inl.h"  // for GetObjectClassName, InitClass, NewObjectWithGivenProtoAndKind, ToPropertyKey
#include "vm/NativeObject-inl.h"      // for NativeObject::global
#include "vm/ObjectOperations-inl.h"  // for DeleteProperty, GetProperty
#include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm

using namespace js;

using JS::AutoStableStringChars;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;

const JSClassOps DebuggerObject::classOps_ = {
    nullptr,                          // addProperty
    nullptr,                          // delProperty
    nullptr,                          // enumerate
    nullptr,                          // newEnumerate
    nullptr,                          // resolve
    nullptr,                          // mayResolve
    nullptr,                          // finalize
    nullptr,                          // call
    nullptr,                          // construct
    CallTraceMethod<DebuggerObject>,  // trace
};

const JSClass DebuggerObject::class_ = {
    "Object",
    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
    &classOps_,
};

void DebuggerObject::trace(JSTracer* trc) {
  // There is a barrier on private pointers, so the Unbarriered marking
  // is okay.
  if (JSObject* referent = maybeReferent()) {
    TraceManuallyBarrieredCrossCompartmentEdge(trc, this, &referent,
                                               "Debugger.Object referent");
    if (referent != maybeReferent()) {
      setReservedSlotGCThingAsPrivateUnbarriered(OBJECT_SLOT, referent);
    }
  }
}

static DebuggerObject* DebuggerObject_checkThis(JSContext* cx,
                                                const CallArgs& args) {
  JSObject* thisobj = RequireObject(cx, args.thisv());
  if (!thisobj) {
    return nullptr;
  }
  if (!thisobj->is<DebuggerObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_INCOMPATIBLE_PROTO, "Debugger.Object",
                              "method", thisobj->getClass()->name);
    return nullptr;
  }

  return &thisobj->as<DebuggerObject>();
}

/* static */
bool DebuggerObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
                            "Debugger.Object");
  return false;
}

struct MOZ_STACK_CLASS DebuggerObject::CallData {
  JSContext* cx;
  const CallArgs& args;

  Handle<DebuggerObject*> object;
  RootedObject referent;

  CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerObject*> obj)
      : cx(cx), args(args), object(obj), referent(cx, obj->referent()) {}

  // JSNative properties
  bool callableGetter();
  bool isBoundFunctionGetter();
  bool isArrowFunctionGetter();
  bool isAsyncFunctionGetter();
  bool isClassConstructorGetter();
  bool isGeneratorFunctionGetter();
  bool protoGetter();
  bool classGetter();
  bool nameGetter();
  bool displayNameGetter();
  bool parameterNamesGetter();
  bool scriptGetter();
  bool environmentGetter();
  bool boundTargetFunctionGetter();
  bool boundThisGetter();
  bool boundArgumentsGetter();
  bool allocationSiteGetter();
  bool isErrorGetter();
  bool isMutedErrorGetter();
  bool errorMessageNameGetter();
  bool errorNotesGetter();
  bool errorLineNumberGetter();
  bool errorColumnNumberGetter();
  bool isProxyGetter();
  bool proxyTargetGetter();
  bool proxyHandlerGetter();
  bool isPromiseGetter();
  bool promiseStateGetter();
  bool promiseValueGetter();
  bool promiseReasonGetter();
  bool promiseLifetimeGetter();
  bool promiseTimeToResolutionGetter();
  bool promiseAllocationSiteGetter();
  bool promiseResolutionSiteGetter();
  bool promiseIDGetter();
  bool promiseDependentPromisesGetter();

  // JSNative methods
  bool isExtensibleMethod();
  bool isSealedMethod();
  bool isFrozenMethod();
  bool getPropertyMethod();
  bool setPropertyMethod();
  bool getOwnPropertyNamesMethod();
  bool getOwnPropertyNamesLengthMethod();
  bool getOwnPropertySymbolsMethod();
  bool getOwnPrivatePropertiesMethod();
  bool getOwnPropertyDescriptorMethod();
  bool preventExtensionsMethod();
  bool sealMethod();
  bool freezeMethod();
  bool definePropertyMethod();
  bool definePropertiesMethod();
  bool deletePropertyMethod();
  bool callMethod();
  bool applyMethod();
  bool asEnvironmentMethod();
  bool forceLexicalInitializationByNameMethod();
  bool executeInGlobalMethod();
  bool executeInGlobalWithBindingsMethod();
  bool createSource();
  bool makeDebuggeeValueMethod();
  bool isSameNativeMethod();
  bool isSameNativeWithJitInfoMethod();
  bool isNativeGetterWithJitInfo();
  bool unsafeDereferenceMethod();
  bool unwrapMethod();
  bool getPromiseReactionsMethod();

  using Method = bool (CallData::*)();

  template <Method MyMethod>
  static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
};

template <DebuggerObject::CallData::Method MyMethod>
/* static */
bool DebuggerObject::CallData::ToNative(JSContext* cx, unsigned argc,
                                        Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  Rooted<DebuggerObject*> obj(cx, DebuggerObject_checkThis(cx, args));
  if (!obj) {
    return false;
  }

  CallData data(cx, args, obj);
  return (data.*MyMethod)();
}

bool DebuggerObject::CallData::callableGetter() {
  args.rval().setBoolean(object->isCallable());
  return true;
}

bool DebuggerObject::CallData::isBoundFunctionGetter() {
  args.rval().setBoolean(object->isBoundFunction());
  return true;
}

bool DebuggerObject::CallData::isArrowFunctionGetter() {
  if (!object->isDebuggeeFunction()) {
    args.rval().setUndefined();
    return true;
  }

  args.rval().setBoolean(object->isArrowFunction());
  return true;
}

bool DebuggerObject::CallData::isAsyncFunctionGetter() {
  if (!object->isDebuggeeFunction()) {
    args.rval().setUndefined();
    return true;
  }

  args.rval().setBoolean(object->isAsyncFunction());
  return true;
}

bool DebuggerObject::CallData::isGeneratorFunctionGetter() {
  if (!object->isDebuggeeFunction()) {
    args.rval().setUndefined();
    return true;
  }

  args.rval().setBoolean(object->isGeneratorFunction());
  return true;
}

bool DebuggerObject::CallData::isClassConstructorGetter() {
  if (!object->isDebuggeeFunction()) {
    args.rval().setUndefined();
    return true;
  }

  args.rval().setBoolean(object->isClassConstructor());
  return true;
}

bool DebuggerObject::CallData::protoGetter() {
  Rooted<DebuggerObject*> result(cx);
  if (!DebuggerObject::getPrototypeOf(cx, object, &result)) {
    return false;
  }

  args.rval().setObjectOrNull(result);
  return true;
}

bool DebuggerObject::CallData::classGetter() {
  RootedString result(cx);
  if (!DebuggerObject::getClassName(cx, object, &result)) {
    return false;
  }

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

bool DebuggerObject::CallData::nameGetter() {
  if (!object->isFunction() && !object->isBoundFunction()) {
    args.rval().setUndefined();
    return true;
  }

  JS::Rooted<JSAtom*> result(cx);
  if (!object->name(cx, &result)) {
    return false;
  }

  if (result) {
    args.rval().setString(result);
  } else {
    args.rval().setUndefined();
  }
  return true;
}

bool DebuggerObject::CallData::displayNameGetter() {
  if (!object->isFunction() && !object->isBoundFunction()) {
    args.rval().setUndefined();
    return true;
  }

  JS::Rooted<JSAtom*> result(cx);
  if (!object->displayName(cx, &result)) {
    return false;
  }
  if (result) {
    args.rval().setString(result);
  } else {
    args.rval().setUndefined();
  }
  return true;
}

bool DebuggerObject::CallData::parameterNamesGetter() {
  if (!object->isDebuggeeFunction()) {
    args.rval().setUndefined();
    return true;
  }

  RootedFunction referent(cx, &object->referent()->as<JSFunction>());

  ArrayObject* arr = GetFunctionParameterNamesArray(cx, referent);
  if (!arr) {
    return false;
  }

  args.rval().setObject(*arr);
  return true;
}

bool DebuggerObject::CallData::scriptGetter() {
  Debugger* dbg = object->owner();

  if (!referent->is<JSFunction>()) {
    args.rval().setUndefined();
    return true;
  }

  RootedFunction fun(cx, &referent->as<JSFunction>());
  if (!IsInterpretedNonSelfHostedFunction(fun)) {
    args.rval().setUndefined();
    return true;
  }

  RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
  if (!script) {
    return false;
  }

  // Only hand out debuggee scripts.
  if (!dbg->observesScript(script)) {
    args.rval().setNull();
    return true;
  }

  Rooted<DebuggerScript*> scriptObject(cx, dbg->wrapScript(cx, script));
  if (!scriptObject) {
    return false;
  }

  args.rval().setObject(*scriptObject);
  return true;
}

bool DebuggerObject::CallData::environmentGetter() {
  Debugger* dbg = object->owner();

  // Don't bother switching compartments just to check obj's type and get its
  // env.
  if (!referent->is<JSFunction>()) {
    args.rval().setUndefined();
    return true;
  }

  RootedFunction fun(cx, &referent->as<JSFunction>());
  if (!IsInterpretedNonSelfHostedFunction(fun)) {
    args.rval().setUndefined();
    return true;
  }

  // Only hand out environments of debuggee functions.
  if (!dbg->observesGlobal(&fun->global())) {
    args.rval().setNull();
    return true;
  }

  Rooted<Env*> env(cx);
  {
    AutoRealm ar(cx, fun);
    env = GetDebugEnvironmentForFunction(cx, fun);
    if (!env) {
      return false;
    }
  }

  return dbg->wrapEnvironment(cx, env, args.rval());
}

bool DebuggerObject::CallData::boundTargetFunctionGetter() {
  if (!object->isDebuggeeBoundFunction()) {
    args.rval().setUndefined();
    return true;
  }

  Rooted<DebuggerObject*> result(cx);
  if (!DebuggerObject::getBoundTargetFunction(cx, object, &result)) {
    return false;
  }

  args.rval().setObject(*result);
  return true;
}

bool DebuggerObject::CallData::boundThisGetter() {
  if (!object->isDebuggeeBoundFunction()) {
    args.rval().setUndefined();
    return true;
  }

  return DebuggerObject::getBoundThis(cx, object, args.rval());
}

bool DebuggerObject::CallData::boundArgumentsGetter() {
  if (!object->isDebuggeeBoundFunction()) {
    args.rval().setUndefined();
    return true;
  }

  Rooted<ValueVector> result(cx, ValueVector(cx));
  if (!DebuggerObject::getBoundArguments(cx, object, &result)) {
    return false;
  }

  RootedObject obj(cx,
                   NewDenseCopiedArray(cx, result.length(), result.begin()));
  if (!obj) {
    return false;
  }

  args.rval().setObject(*obj);
  return true;
}

bool DebuggerObject::CallData::allocationSiteGetter() {
  RootedObject result(cx);
  if (!DebuggerObject::getAllocationSite(cx, object, &result)) {
    return false;
  }

  args.rval().setObjectOrNull(result);
  return true;
}

// Returns the "name" field (see js/public/friend/ErrorNumbers.msg), which may
// be used as a unique identifier, for any error object with a JSErrorReport or
// undefined if the object has no JSErrorReport.
bool DebuggerObject::CallData::errorMessageNameGetter() {
  RootedString result(cx);
  if (!DebuggerObject::getErrorMessageName(cx, object, &result)) {
    return false;
  }

  if (result) {
    args.rval().setString(result);
  } else {
    args.rval().setUndefined();
  }
  return true;
}

bool DebuggerObject::CallData::isErrorGetter() {
  args.rval().setBoolean(object->isError());
  return true;
}

bool DebuggerObject::CallData::isMutedErrorGetter() {
  args.rval().setBoolean(object->isMutedError(cx));
  return true;
}

bool DebuggerObject::CallData::errorNotesGetter() {
  return DebuggerObject::getErrorNotes(cx, object, args.rval());
}

bool DebuggerObject::CallData::errorLineNumberGetter() {
  return DebuggerObject::getErrorLineNumber(cx, object, args.rval());
}

bool DebuggerObject::CallData::errorColumnNumberGetter() {
  return DebuggerObject::getErrorColumnNumber(cx, object, args.rval());
}

bool DebuggerObject::CallData::isProxyGetter() {
  args.rval().setBoolean(object->isScriptedProxy());
  return true;
}

bool DebuggerObject::CallData::proxyTargetGetter() {
  if (!object->isScriptedProxy()) {
    args.rval().setUndefined();
    return true;
  }

  Rooted<DebuggerObject*> result(cx);
  if (!DebuggerObject::getScriptedProxyTarget(cx, object, &result)) {
    return false;
  }

  args.rval().setObjectOrNull(result);
  return true;
}

bool DebuggerObject::CallData::proxyHandlerGetter() {
  if (!object->isScriptedProxy()) {
    args.rval().setUndefined();
    return true;
  }
  Rooted<DebuggerObject*> result(cx);
  if (!DebuggerObject::getScriptedProxyHandler(cx, object, &result)) {
    return false;
  }

  args.rval().setObjectOrNull(result);
  return true;
}

bool DebuggerObject::CallData::isPromiseGetter() {
  args.rval().setBoolean(object->isPromise());
  return true;
}

bool DebuggerObject::CallData::promiseStateGetter() {
  if (!DebuggerObject::requirePromise(cx, object)) {
    return false;
  }

  RootedValue result(cx);
  switch (object->promiseState()) {
    case JS::PromiseState::Pending:
      result.setString(cx->names().pending);
      break;
    case JS::PromiseState::Fulfilled:
      result.setString(cx->names().fulfilled);
      break;
    case JS::PromiseState::Rejected:
      result.setString(cx->names().rejected);
      break;
  }

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

bool DebuggerObject::CallData::promiseValueGetter() {
  if (!DebuggerObject::requirePromise(cx, object)) {
    return false;
  }

  if (object->promiseState() != JS::PromiseState::Fulfilled) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_PROMISE_NOT_FULFILLED);
    return false;
  }

  return DebuggerObject::getPromiseValue(cx, object, args.rval());
  ;
}

bool DebuggerObject::CallData::promiseReasonGetter() {
  if (!DebuggerObject::requirePromise(cx, object)) {
    return false;
  }

  if (object->promiseState() != JS::PromiseState::Rejected) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_PROMISE_NOT_REJECTED);
    return false;
  }

  return DebuggerObject::getPromiseReason(cx, object, args.rval());
}

bool DebuggerObject::CallData::promiseLifetimeGetter() {
  if (!DebuggerObject::requirePromise(cx, object)) {
    return false;
  }

  args.rval().setNumber(object->promiseLifetime());
  return true;
}

bool DebuggerObject::CallData::promiseTimeToResolutionGetter() {
  if (!DebuggerObject::requirePromise(cx, object)) {
    return false;
  }

  if (object->promiseState() == JS::PromiseState::Pending) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_PROMISE_NOT_RESOLVED);
    return false;
  }

  args.rval().setNumber(object->promiseTimeToResolution());
  return true;
}

static PromiseObject* EnsurePromise(JSContext* cx, HandleObject referent) {
  // We only care about promises, so CheckedUnwrapStatic is OK.
  RootedObject obj(cx, CheckedUnwrapStatic(referent));
  if (!obj) {
    ReportAccessDenied(cx);
    return nullptr;
  }
  if (!obj->is<PromiseObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_EXPECTED_TYPE, "Debugger", "Promise",
                              obj->getClass()->name);
    return nullptr;
  }
  return &obj->as<PromiseObject>();
}

bool DebuggerObject::CallData::promiseAllocationSiteGetter() {
  Rooted<PromiseObject*> promise(cx, EnsurePromise(cx, referent));
  if (!promise) {
    return false;
  }

  RootedObject allocSite(cx, promise->allocationSite());
  if (!allocSite) {
    args.rval().setNull();
    return true;
  }

  if (!cx->compartment()->wrap(cx, &allocSite)) {
    return false;
  }
  args.rval().set(ObjectValue(*allocSite));
  return true;
}

bool DebuggerObject::CallData::promiseResolutionSiteGetter() {
  Rooted<PromiseObject*> promise(cx, EnsurePromise(cx, referent));
  if (!promise) {
    return false;
  }

  if (promise->state() == JS::PromiseState::Pending) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_PROMISE_NOT_RESOLVED);
    return false;
  }

  RootedObject resolutionSite(cx, promise->resolutionSite());
  if (!resolutionSite) {
    args.rval().setNull();
    return true;
  }

  if (!cx->compartment()->wrap(cx, &resolutionSite)) {
    return false;
  }
  args.rval().set(ObjectValue(*resolutionSite));
  return true;
}

bool DebuggerObject::CallData::promiseIDGetter() {
  Rooted<PromiseObject*> promise(cx, EnsurePromise(cx, referent));
  if (!promise) {
    return false;
  }

  args.rval().setNumber(double(promise->getID()));
  return true;
}

bool DebuggerObject::CallData::promiseDependentPromisesGetter() {
  Debugger* dbg = object->owner();

  Rooted<PromiseObject*> promise(cx, EnsurePromise(cx, referent));
  if (!promise) {
    return false;
  }

  Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
  {
    JSAutoRealm ar(cx, promise);
    if (!promise->dependentPromises(cx, &values)) {
      return false;
    }
  }
  for (size_t i = 0; i < values.length(); i++) {
    if (!dbg->wrapDebuggeeValue(cx, values[i])) {
      return false;
    }
  }
  Rooted<ArrayObject*> promises(cx);
  if (values.length() == 0) {
    promises = NewDenseEmptyArray(cx);
  } else {
    promises = NewDenseCopiedArray(cx, values.length(), values[0].address());
  }
  if (!promises) {
    return false;
  }
  args.rval().setObject(*promises);
  return true;
}

bool DebuggerObject::CallData::isExtensibleMethod() {
  bool result;
  if (!DebuggerObject::isExtensible(cx, object, result)) {
    return false;
  }

  args.rval().setBoolean(result);
  return true;
}

bool DebuggerObject::CallData::isSealedMethod() {
  bool result;
  if (!DebuggerObject::isSealed(cx, object, result)) {
    return false;
  }

  args.rval().setBoolean(result);
  return true;
}

bool DebuggerObject::CallData::isFrozenMethod() {
  bool result;
  if (!DebuggerObject::isFrozen(cx, object, result)) {
    return false;
  }

  args.rval().setBoolean(result);
  return true;
}

bool DebuggerObject::CallData::getOwnPropertyNamesMethod() {
  RootedIdVector ids(cx);
  if (!DebuggerObject::getOwnPropertyNames(cx, object, &ids)) {
    return false;
  }

  JSObject* obj = IdVectorToArray(cx, ids);
  if (!obj) {
    return false;
  }

  args.rval().setObject(*obj);
  return true;
}

bool DebuggerObject::CallData::getOwnPropertyNamesLengthMethod() {
  size_t ownPropertiesLength;
  if (!DebuggerObject::getOwnPropertyNamesLength(cx, object,
                                                 &ownPropertiesLength)) {
    return false;
  }

  args.rval().setNumber(ownPropertiesLength);
  return true;
}

bool DebuggerObject::CallData::getOwnPropertySymbolsMethod() {
  RootedIdVector ids(cx);
  if (!DebuggerObject::getOwnPropertySymbols(cx, object, &ids)) {
    return false;
  }

  JSObject* obj = IdVectorToArray(cx, ids);
  if (!obj) {
    return false;
  }

  args.rval().setObject(*obj);
  return true;
}

bool DebuggerObject::CallData::getOwnPrivatePropertiesMethod() {
  RootedIdVector ids(cx);
  if (!DebuggerObject::getOwnPrivateProperties(cx, object, &ids)) {
    return false;
  }

  JSObject* obj = IdVectorToArray(cx, ids);
  if (!obj) {
    return false;
  }

  args.rval().setObject(*obj);
  return true;
}

bool DebuggerObject::CallData::getOwnPropertyDescriptorMethod() {
  RootedId id(cx);
  if (!ToPropertyKey(cx, args.get(0), &id)) {
    return false;
  }

  Rooted<Maybe<PropertyDescriptor>> desc(cx);
  if (!DebuggerObject::getOwnPropertyDescriptor(cx, object, id, &desc)) {
    return false;
  }

  return JS::FromPropertyDescriptor(cx, desc, args.rval());
}

bool DebuggerObject::CallData::preventExtensionsMethod() {
  if (!DebuggerObject::preventExtensions(cx, object)) {
    return false;
  }

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

bool DebuggerObject::CallData::sealMethod() {
  if (!DebuggerObject::seal(cx, object)) {
    return false;
  }

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

bool DebuggerObject::CallData::freezeMethod() {
  if (!DebuggerObject::freeze(cx, object)) {
    return false;
  }

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

bool DebuggerObject::CallData::definePropertyMethod() {
  if (!args.requireAtLeast(cx, "Debugger.Object.defineProperty", 2)) {
    return false;
  }

  RootedId id(cx);
  if (!ToPropertyKey(cx, args[0], &id)) {
    return false;
  }

  Rooted<PropertyDescriptor> desc(cx);
  if (!ToPropertyDescriptor(cx, args[1], false, &desc)) {
    return false;
  }

  if (!DebuggerObject::defineProperty(cx, object, id, desc)) {
    return false;
  }

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

bool DebuggerObject::CallData::definePropertiesMethod() {
  if (!args.requireAtLeast(cx, "Debugger.Object.defineProperties", 1)) {
    return false;
  }

  RootedValue arg(cx, args[0]);
  RootedObject props(cx, ToObject(cx, arg));
  if (!props) {
    return false;
  }
  RootedIdVector ids(cx);
  Rooted<PropertyDescriptorVector> descs(cx, PropertyDescriptorVector(cx));
  if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs)) {
    return false;
  }
  Rooted<IdVector> ids2(cx, IdVector(cx));
  if (!ids2.append(ids.begin(), ids.end())) {
    return false;
  }

  if (!DebuggerObject::defineProperties(cx, object, ids2, descs)) {
    return false;
  }

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

/*
 * This does a non-strict delete, as a matter of API design. The case where the
 * property is non-configurable isn't necessarily exceptional here.
 */
bool DebuggerObject::CallData::deletePropertyMethod() {
  RootedId id(cx);
  if (!ToPropertyKey(cx, args.get(0), &id)) {
    return false;
  }

  ObjectOpResult result;
  if (!DebuggerObject::deleteProperty(cx, object, id, result)) {
    return false;
  }

  args.rval().setBoolean(result.ok());
  return true;
}

bool DebuggerObject::CallData::callMethod() {
  RootedValue thisv(cx, args.get(0));

  Rooted<ValueVector> nargs(cx, ValueVector(cx));
  if (args.length() >= 2) {
    if (!nargs.growBy(args.length() - 1)) {
      return false;
    }
    for (size_t i = 1; i < args.length(); ++i) {
      nargs[i - 1].set(args[i]);
    }
  }

  Rooted<Maybe<Completion>> completion(
      cx, DebuggerObject::call(cx, object, thisv, nargs));
  if (!completion.get()) {
    return false;
  }

  return completion->buildCompletionValue(cx, object->owner(), args.rval());
}

bool DebuggerObject::CallData::getPropertyMethod() {
  Debugger* dbg = object->owner();

  RootedId id(cx);
  if (!ToPropertyKey(cx, args.get(0), &id)) {
    return false;
  }

  RootedValue receiver(cx,
                       args.length() < 2 ? ObjectValue(*object) : args.get(1));

  Rooted<Completion> comp(cx);
  JS_TRY_VAR_OR_RETURN_FALSE(cx, comp, getProperty(cx, object, id, receiver));
  return comp.get().buildCompletionValue(cx, dbg, args.rval());
}

bool DebuggerObject::CallData::setPropertyMethod() {
  Debugger* dbg = object->owner();

  RootedId id(cx);
  if (!ToPropertyKey(cx, args.get(0), &id)) {
    return false;
  }

  RootedValue value(cx, args.get(1));

  RootedValue receiver(cx,
                       args.length() < 3 ? ObjectValue(*object) : args.get(2));

  Rooted<Completion> comp(cx);
  JS_TRY_VAR_OR_RETURN_FALSE(cx, comp,
                             setProperty(cx, object, id, value, receiver));
  return comp.get().buildCompletionValue(cx, dbg, args.rval());
}

bool DebuggerObject::CallData::applyMethod() {
  RootedValue thisv(cx, args.get(0));

  Rooted<ValueVector> nargs(cx, ValueVector(cx));
  if (args.length() >= 2 && !args[1].isNullOrUndefined()) {
    if (!args[1].isObject()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_BAD_APPLY_ARGS, "apply");
      return false;
    }

    RootedObject argsobj(cx, &args[1].toObject());

    uint64_t argc = 0;
    if (!GetLengthProperty(cx, argsobj, &argc)) {
      return false;
    }
    argc = std::min(argc, uint64_t(ARGS_LENGTH_MAX));

    if (!nargs.growBy(argc) || !GetElements(cx, argsobj, argc, nargs.begin())) {
      return false;
    }
  }

  Rooted<Maybe<Completion>> completion(
      cx, DebuggerObject::call(cx, object, thisv, nargs));
  if (!completion.get()) {
    return false;
  }

  return completion->buildCompletionValue(cx, object->owner(), args.rval());
}

static void EnterDebuggeeObjectRealm(JSContext* cx, Maybe<AutoRealm>& ar,
                                     JSObject* referent) {
  // |referent| may be a cross-compartment wrapper and CCWs normally
  // shouldn't be used with AutoRealm, but here we use an arbitrary realm for
  // now because we don't really have another option.
  ar.emplace(cx, referent->maybeCCWRealm()->maybeGlobal());
}

static bool RequireGlobalObject(JSContext* cx, HandleValue dbgobj,
                                HandleObject referent) {
  RootedObject obj(cx, referent);

  if (!obj->is<GlobalObject>()) {
    const char* isWrapper = "";
    const char* isWindowProxy = "";

    // Help the poor programmer by pointing out wrappers around globals...
    if (obj->is<WrapperObject>()) {
      obj = js::UncheckedUnwrap(obj);
      isWrapper = "a wrapper around ";
    }

    // ... and WindowProxies around Windows.
    if (IsWindowProxy(obj)) {
      obj = ToWindowIfWindowProxy(obj);
      isWindowProxy = "a WindowProxy referring to ";
    }

    if (obj->is<GlobalObject>()) {
      ReportValueError(cx, JSMSG_DEBUG_WRAPPER_IN_WAY, JSDVG_SEARCH_STACK,
                       dbgobj, nullptr, isWrapper, isWindowProxy);
    } else {
      ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, dbgobj,
                       nullptr, "a global object");
    }
    return false;
  }

  return true;
}

bool DebuggerObject::CallData::asEnvironmentMethod() {
  Debugger* dbg = object->owner();

  if (!RequireGlobalObject(cx, args.thisv(), referent)) {
    return false;
  }

  Rooted<Env*> env(cx);
  {
    AutoRealm ar(cx, referent);
    env = GetDebugEnvironmentForGlobalLexicalEnvironment(cx);
    if (!env) {
      return false;
    }
  }

  return dbg->wrapEnvironment(cx, env, args.rval());
}

// Lookup a binding on the referent's global scope and change it to undefined
// if it is an uninitialized lexical, otherwise do nothing. The method's
// JavaScript return value is true _only_ when an uninitialized lexical has been
// altered, otherwise it is false.
bool DebuggerObject::CallData::forceLexicalInitializationByNameMethod() {
  if (!args.requireAtLeast(
          cx, "Debugger.Object.prototype.forceLexicalInitializationByName",
          1)) {
    return false;
  }

  if (!DebuggerObject::requireGlobal(cx, object)) {
    return false;
  }

  RootedId id(cx);
  if (!ValueToIdentifier(cx, args[0], &id)) {
    return false;
  }

  bool result;
  if (!DebuggerObject::forceLexicalInitializationByName(cx, object, id,
                                                        result)) {
    return false;
  }

  args.rval().setBoolean(result);
  return true;
}

bool DebuggerObject::CallData::executeInGlobalMethod() {
  if (!args.requireAtLeast(cx, "Debugger.Object.prototype.executeInGlobal",
                           1)) {
    return false;
  }

  if (!DebuggerObject::requireGlobal(cx, object)) {
    return false;
  }

  AutoStableStringChars stableChars(cx);
  if (!ValueToStableChars(cx, "Debugger.Object.prototype.executeInGlobal",
                          args[0], stableChars)) {
    return false;
  }
  mozilla::Range<const char16_t> chars = stableChars.twoByteRange();

  EvalOptions options(EvalOptions::EnvKind::Global);
  if (!ParseEvalOptions(cx, args.get(1), options)) {
    return false;
  }

  Rooted<Completion> comp(cx);
  JS_TRY_VAR_OR_RETURN_FALSE(
      cx, comp,
      DebuggerObject::executeInGlobal(cx, object, chars, nullptr, options));
  return comp.get().buildCompletionValue(cx, object->owner(), args.rval());
}

bool DebuggerObject::CallData::executeInGlobalWithBindingsMethod() {
  if (!args.requireAtLeast(
          cx, "Debugger.Object.prototype.executeInGlobalWithBindings", 2)) {
    return false;
  }

  if (!DebuggerObject::requireGlobal(cx, object)) {
    return false;
  }

  AutoStableStringChars stableChars(cx);
  if (!ValueToStableChars(
          cx, "Debugger.Object.prototype.executeInGlobalWithBindings", args[0],
          stableChars)) {
    return false;
  }
  mozilla::Range<const char16_t> chars = stableChars.twoByteRange();

  RootedObject bindings(cx, RequireObject(cx, args[1]));
  if (!bindings) {
    return false;
  }

  EvalOptions options(EvalOptions::EnvKind::GlobalWithExtraOuterBindings);
  if (!ParseEvalOptions(cx, args.get(2), options)) {
    return false;
  }

  Rooted<Completion> comp(cx);
  JS_TRY_VAR_OR_RETURN_FALSE(
      cx, comp,
      DebuggerObject::executeInGlobal(cx, object, chars, bindings, options));
  return comp.get().buildCompletionValue(cx, object->owner(), args.rval());
}

// Copy a narrow or wide string to a vector, appending a null terminator.
template <typename T>
static bool CopyStringToVector(JSContext* cx, JSString* str, Vector<T>& chars) {
  JSLinearString* linear = str->ensureLinear(cx);
  if (!linear) {
    return false;
  }
  if (!chars.appendN(0, linear->length() + 1)) {
    return false;
  }
  CopyChars(chars.begin(), *linear);
  return true;
}

bool DebuggerObject::CallData::createSource() {
  if (!args.requireAtLeast(cx, "Debugger.Object.prototype.createSource", 1)) {
    return false;
  }

  if (!DebuggerObject::requireGlobal(cx, object)) {
    return false;
  }

  Debugger* dbg = object->owner();
  if (!dbg->isDebuggeeUnbarriered(referent->as<GlobalObject>().realm())) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Object",
                              "global");
    return false;
  }

  RootedObject options(cx, ToObject(cx, args[0]));
  if (!options) {
    return false;
  }

  RootedValue v(cx);
  if (!JS_GetProperty(cx, options, "text", &v)) {
    return false;
  }

  RootedString text(cx, ToString<CanGC>(cx, v));
  if (!text) {
    return false;
  }

  if (!JS_GetProperty(cx, options, "url", &v)) {
    return false;
  }

  RootedString url(cx, ToString<CanGC>(cx, v));
  if (!url) {
    return false;
  }

  if (!JS_GetProperty(cx, options, "startLine", &v)) {
    return false;
  }

  uint32_t startLine;
  if (!ToUint32(cx, v, &startLine)) {
    return false;
  }

  if (!JS_GetProperty(cx, options, "startColumn", &v)) {
    return false;
  }

  uint32_t startColumn;
  if (!ToUint32(cx, v, &startColumn)) {
    return false;
  }
  if (startColumn == 0) {
    startColumn = 1;
  }

  if (!JS_GetProperty(cx, options, "sourceMapURL", &v)) {
    return false;
  }

  RootedString sourceMapURL(cx);
  if (!v.isUndefined()) {
    sourceMapURL = ToString<CanGC>(cx, v);
    if (!sourceMapURL) {
      return false;
    }
  }

  if (!JS_GetProperty(cx, options, "isScriptElement", &v)) {
    return false;
  }

  bool isScriptElement = ToBoolean(v);

  if (!JS_GetProperty(cx, options, "forceEnableAsmJS", &v)) {
    return false;
  }

  bool forceEnableAsmJS = ToBoolean(v);

  RootedScript script(cx);
  {
    AutoRealm ar(cx, referent);

    JS::CompileOptions compileOptions(cx);
    compileOptions.lineno = startLine;
    compileOptions.column = JS::ColumnNumberOneOrigin(startColumn);
    if (forceEnableAsmJS) {
      compileOptions.setAsmJSOption(JS::AsmJSOption::Enabled);
    }

    if (!JS::StringHasLatin1Chars(url)) {
      JS_ReportErrorASCII(cx, "URL must be a narrow string");
      return false;
    }

    UniqueChars urlChars = JS_EncodeStringToUTF8(cx, url);
    if (!urlChars) {
      return false;
    }
    compileOptions.setFile(urlChars.get());

    Vector<char16_t> sourceMapURLChars(cx);
    if (sourceMapURL) {
      if (!CopyStringToVector(cx, sourceMapURL, sourceMapURLChars)) {
        return false;
      }
      compileOptions.setSourceMapURL(sourceMapURLChars.begin());
    }

    if (isScriptElement) {
      // The introduction type must be a statically allocated string.
      compileOptions.setIntroductionType("inlineScript");
    }

    AutoStableStringChars linearChars(cx);
    if (!linearChars.initTwoByte(cx, text)) {
      return false;
    }
    JS::SourceText<char16_t> srcBuf;
    if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
      return false;
    }

    script = JS::Compile(cx, compileOptions, srcBuf);
    if (!script) {
      return false;
    }
  }

  Rooted<ScriptSourceObject*> sso(cx, script->sourceObject());
  RootedObject wrapped(cx, dbg->wrapSource(cx, sso));
  if (!wrapped) {
    return false;
  }

  args.rval().setObject(*wrapped);
  return true;
}

bool DebuggerObject::CallData::makeDebuggeeValueMethod() {
  if (!args.requireAtLeast(cx, "Debugger.Object.prototype.makeDebuggeeValue",
                           1)) {
    return false;
  }

  return DebuggerObject::makeDebuggeeValue(cx, object, args[0], args.rval());
}

bool DebuggerObject::CallData::isSameNativeMethod() {
  if (!args.requireAtLeast(cx, "Debugger.Object.prototype.isSameNative", 1)) {
    return false;
  }

  return DebuggerObject::isSameNative(cx, object, args[0], CheckJitInfo::No,
                                      args.rval());
}

bool DebuggerObject::CallData::isSameNativeWithJitInfoMethod() {
  if (!args.requireAtLeast(
          cx, "Debugger.Object.prototype.isSameNativeWithJitInfo", 1)) {
    return false;
  }

  return DebuggerObject::isSameNative(cx, object, args[0], CheckJitInfo::Yes,
                                      args.rval());
}

bool DebuggerObject::CallData::isNativeGetterWithJitInfo() {
  return DebuggerObject::isNativeGetterWithJitInfo(cx, object, args.rval());
}

bool DebuggerObject::CallData::unsafeDereferenceMethod() {
  RootedObject result(cx);
  if (!DebuggerObject::unsafeDereference(cx, object, &result)) {
    return false;
  }

  args.rval().setObject(*result);
  return true;
}

bool DebuggerObject::CallData::unwrapMethod() {
  Rooted<DebuggerObject*> result(cx);
  if (!DebuggerObject::unwrap(cx, object, &result)) {
    return false;
  }

  args.rval().setObjectOrNull(result);
  return true;
}

struct DebuggerObject::PromiseReactionRecordBuilder
    : js::PromiseReactionRecordBuilder {
  Debugger* dbg;
  Handle<ArrayObject*> records;

  PromiseReactionRecordBuilder(Debugger* dbg, Handle<ArrayObject*> records)
      : dbg(dbg), records(records) {}

  bool then(JSContext* cx, HandleObject resolve, HandleObject reject,
            HandleObject result) override {
    Rooted<PlainObject*> record(cx, NewPlainObject(cx));
    if (!record) {
      return false;
    }

    if (!setIfNotNull(cx, record, cx->names().resolve, resolve) ||
        !setIfNotNull(cx, record, cx->names().reject, reject) ||
        !setIfNotNull(cx, record, cx->names().result, result)) {
      return false;
    }

    return push(cx, record);
  }

  bool direct(JSContext* cx, Handle<PromiseObject*> unwrappedPromise) override {
    RootedValue v(cx, ObjectValue(*unwrappedPromise));
    return dbg->wrapDebuggeeValue(cx, &v) && push(cx, v);
  }

  bool asyncFunction(
      JSContext* cx,
      Handle<AsyncFunctionGeneratorObject*> unwrappedGenerator) override {
    return maybePushGenerator(cx, unwrappedGenerator);
  }

  bool asyncGenerator(
      JSContext* cx,
      Handle<AsyncGeneratorObject*> unwrappedGenerator) override {
    return maybePushGenerator(cx, unwrappedGenerator);
  }

 private:
  bool push(JSContext* cx, HandleObject record) {
    RootedValue recordVal(cx, ObjectValue(*record));
    return push(cx, recordVal);
  }

  bool push(JSContext* cx, HandleValue recordVal) {
    return NewbornArrayPush(cx, records, recordVal);
  }

  bool maybePushGenerator(JSContext* cx,
                          Handle<AbstractGeneratorObject*> unwrappedGenerator) {
    Rooted<DebuggerFrame*> frame(cx);
    if (unwrappedGenerator->isClosed()) {
      // If the generator is closed, we can't generate a DebuggerFrame for it,
      // so we ignore it.
      return true;
    }
    if (!unwrappedGenerator->realm()->isDebuggee()) {
      // Caller can keep the reference to the debugger object even after
      // removing the realm from debuggee.  Do nothing for this case.
      return true;
    }
    return dbg->getFrame(cx, unwrappedGenerator, &frame) && push(cx, frame);
  }

  bool setIfNotNull(JSContext* cx, Handle<PlainObject*> obj,
                    Handle<PropertyName*> name, HandleObject prop) {
    if (!prop) {
      return true;
    }

    RootedValue v(cx, ObjectValue(*prop));
    if (!dbg->wrapDebuggeeValue(cx, &v) ||
        !DefineDataProperty(cx, obj, name, v)) {
      return false;
    }

    return true;
  }
};

bool DebuggerObject::CallData::getPromiseReactionsMethod() {
  Debugger* dbg = object->owner();

  Rooted<PromiseObject*> unwrappedPromise(cx, EnsurePromise(cx, referent));
  if (!unwrappedPromise) {
    return false;
  }

  Rooted<ArrayObject*> holder(cx, NewDenseEmptyArray(cx));
  if (!holder) {
    return false;
  }

  PromiseReactionRecordBuilder builder(dbg, holder);
  if (!unwrappedPromise->forEachReactionRecord(cx, builder)) {
    return false;
  }

  args.rval().setObject(*builder.records);
  return true;
}

const JSPropertySpec DebuggerObject::properties_[] = {
    JS_DEBUG_PSG("callable", callableGetter),
    JS_DEBUG_PSG("isBoundFunction", isBoundFunctionGetter),
    JS_DEBUG_PSG("isArrowFunction", isArrowFunctionGetter),
    JS_DEBUG_PSG("isGeneratorFunction", isGeneratorFunctionGetter),
    JS_DEBUG_PSG("isAsyncFunction", isAsyncFunctionGetter),
    JS_DEBUG_PSG("isClassConstructor", isClassConstructorGetter),
    JS_DEBUG_PSG("proto", protoGetter),
    JS_DEBUG_PSG("class", classGetter),
    JS_DEBUG_PSG("name", nameGetter),
    JS_DEBUG_PSG("displayName", displayNameGetter),
    JS_DEBUG_PSG("parameterNames", parameterNamesGetter),
    JS_DEBUG_PSG("script", scriptGetter),
    JS_DEBUG_PSG("environment", environmentGetter),
    JS_DEBUG_PSG("boundTargetFunction", boundTargetFunctionGetter),
    JS_DEBUG_PSG("boundThis", boundThisGetter),
    JS_DEBUG_PSG("boundArguments", boundArgumentsGetter),
    JS_DEBUG_PSG("allocationSite", allocationSiteGetter),
    JS_DEBUG_PSG("isError", isErrorGetter),
    JS_DEBUG_PSG("isMutedError", isMutedErrorGetter),
    JS_DEBUG_PSG("errorMessageName", errorMessageNameGetter),
    JS_DEBUG_PSG("errorNotes", errorNotesGetter),
    JS_DEBUG_PSG("errorLineNumber", errorLineNumberGetter),
    JS_DEBUG_PSG("errorColumnNumber", errorColumnNumberGetter),
    JS_DEBUG_PSG("isProxy", isProxyGetter),
    JS_DEBUG_PSG("proxyTarget", proxyTargetGetter),
    JS_DEBUG_PSG("proxyHandler", proxyHandlerGetter),
    JS_PS_END,
};

const JSPropertySpec DebuggerObject::promiseProperties_[] = {
    JS_DEBUG_PSG("isPromise", isPromiseGetter),
    JS_DEBUG_PSG("promiseState", promiseStateGetter),
    JS_DEBUG_PSG("promiseValue", promiseValueGetter),
    JS_DEBUG_PSG("promiseReason", promiseReasonGetter),
    JS_DEBUG_PSG("promiseLifetime", promiseLifetimeGetter),
    JS_DEBUG_PSG("promiseTimeToResolution", promiseTimeToResolutionGetter),
    JS_DEBUG_PSG("promiseAllocationSite", promiseAllocationSiteGetter),
    JS_DEBUG_PSG("promiseResolutionSite", promiseResolutionSiteGetter),
    JS_DEBUG_PSG("promiseID", promiseIDGetter),
    JS_DEBUG_PSG("promiseDependentPromises", promiseDependentPromisesGetter),
    JS_PS_END,
};

const JSFunctionSpec DebuggerObject::methods_[] = {
    JS_DEBUG_FN("isExtensible", isExtensibleMethod, 0),
    JS_DEBUG_FN("isSealed", isSealedMethod, 0),
    JS_DEBUG_FN("isFrozen", isFrozenMethod, 0),
    JS_DEBUG_FN("getProperty", getPropertyMethod, 0),
    JS_DEBUG_FN("setProperty", setPropertyMethod, 0),
    JS_DEBUG_FN("getOwnPropertyNames", getOwnPropertyNamesMethod, 0),
    JS_DEBUG_FN("getOwnPropertyNamesLength", getOwnPropertyNamesLengthMethod,
                0),
    JS_DEBUG_FN("getOwnPropertySymbols", getOwnPropertySymbolsMethod, 0),
    JS_DEBUG_FN("getOwnPrivateProperties", getOwnPrivatePropertiesMethod, 0),
    JS_DEBUG_FN("getOwnPropertyDescriptor", getOwnPropertyDescriptorMethod, 1),
    JS_DEBUG_FN("preventExtensions", preventExtensionsMethod, 0),
    JS_DEBUG_FN("seal", sealMethod, 0),
    JS_DEBUG_FN("freeze", freezeMethod, 0),
    JS_DEBUG_FN("defineProperty", definePropertyMethod, 2),
    JS_DEBUG_FN("defineProperties", definePropertiesMethod, 1),
    JS_DEBUG_FN("deleteProperty", deletePropertyMethod, 1),
    JS_DEBUG_FN("call", callMethod, 0),
    JS_DEBUG_FN("apply", applyMethod, 0),
    JS_DEBUG_FN("asEnvironment", asEnvironmentMethod, 0),
    JS_DEBUG_FN("forceLexicalInitializationByName",
                forceLexicalInitializationByNameMethod, 1),
    JS_DEBUG_FN("executeInGlobal", executeInGlobalMethod, 1),
    JS_DEBUG_FN("executeInGlobalWithBindings",
                executeInGlobalWithBindingsMethod, 2),
    JS_DEBUG_FN("createSource", createSource, 1),
    JS_DEBUG_FN("makeDebuggeeValue", makeDebuggeeValueMethod, 1),
    JS_DEBUG_FN("isSameNative", isSameNativeMethod, 1),
    JS_DEBUG_FN("isSameNativeWithJitInfo", isSameNativeWithJitInfoMethod, 1),
    JS_DEBUG_FN("isNativeGetterWithJitInfo", isNativeGetterWithJitInfo, 1),
    JS_DEBUG_FN("unsafeDereference", unsafeDereferenceMethod, 0),
    JS_DEBUG_FN("unwrap", unwrapMethod, 0),
    JS_DEBUG_FN("getPromiseReactions", getPromiseReactionsMethod, 0),
    JS_FS_END,
};

/* static */
NativeObject* DebuggerObject::initClass(JSContext* cx,
                                        Handle<GlobalObject*> global,
                                        HandleObject debugCtor) {
  Rooted<NativeObject*> objectProto(
      cx, InitClass(cx, debugCtor, nullptr, nullptr, "Object", construct, 0,
                    properties_, methods_, nullptr, nullptr));

  if (!objectProto) {
    return nullptr;
  }

  if (!DefinePropertiesAndFunctions(cx, objectProto, promiseProperties_,
                                    nullptr)) {
    return nullptr;
  }

  return objectProto;
}

/* static */
DebuggerObject* DebuggerObject::create(JSContext* cx, HandleObject proto,
                                       HandleObject referent,
                                       Handle<NativeObject*> debugger) {
  DebuggerObject* obj =
      IsInsideNursery(referent)
          ? NewObjectWithGivenProto<DebuggerObject>(cx, proto)
          : NewTenuredObjectWithGivenProto<DebuggerObject>(cx, proto);
  if (!obj) {
    return nullptr;
  }

  obj->setReservedSlotGCThingAsPrivate(OBJECT_SLOT, referent);
  obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));

  return obj;
}

bool DebuggerObject::isCallable() const { return referent()->isCallable(); }

bool DebuggerObject::isFunction() const { return referent()->is<JSFunction>(); }

bool DebuggerObject::isDebuggeeFunction() const {
  return referent()->is<JSFunction>() &&
         owner()->observesGlobal(&referent()->as<JSFunction>().global());
}

bool DebuggerObject::isBoundFunction() const {
  return referent()->is<BoundFunctionObject>();
}

bool DebuggerObject::isDebuggeeBoundFunction() const {
  return referent()->is<BoundFunctionObject>() &&
         owner()->observesGlobal(
             &referent()->as<BoundFunctionObject>().global());
}

bool DebuggerObject::isArrowFunction() const {
  MOZ_ASSERT(isDebuggeeFunction());

  return referent()->as<JSFunction>().isArrow();
}

bool DebuggerObject::isAsyncFunction() const {
  MOZ_ASSERT(isDebuggeeFunction());

  return referent()->as<JSFunction>().isAsync();
}

bool DebuggerObject::isGeneratorFunction() const {
  MOZ_ASSERT(isDebuggeeFunction());

  return referent()->as<JSFunction>().isGenerator();
}

bool DebuggerObject::isClassConstructor() const {
  MOZ_ASSERT(isDebuggeeFunction());

  return referent()->as<JSFunction>().isClassConstructor();
}

bool DebuggerObject::isGlobal() const { return referent()->is<GlobalObject>(); }

bool DebuggerObject::isScriptedProxy() const {
  return js::IsScriptedProxy(referent());
}

bool DebuggerObject::isPromise() const {
  JSObject* referent = this->referent();

  if (IsCrossCompartmentWrapper(referent)) {
    // We only care about promises, so CheckedUnwrapStatic is OK.
    referent = CheckedUnwrapStatic(referent);
    if (!referent) {
      return false;
    }
  }

  return referent->is<PromiseObject>();
}

bool DebuggerObject::isError() const {
  JSObject* referent = this->referent();

  if (IsCrossCompartmentWrapper(referent)) {
    // We only check for error classes, so CheckedUnwrapStatic is OK.
    referent = CheckedUnwrapStatic(referent);
    if (!referent) {
      return false;
    }
  }

  return referent->is<ErrorObject>();
}

bool DebuggerObject::isMutedError(JSContext* cx) const {
  JS::Rooted<JSObject*> referent(cx, this->referent());

  if (IsCrossCompartmentWrapper(referent)) {
    // We only check for error classes, so CheckedUnwrapStatic is OK.
    referent = CheckedUnwrapStatic(referent);
    if (!referent) {
      return false;
    }
  }

  if (!referent->is<ErrorObject>()) {
    return false;
  }

  JSErrorReport* report = referent->as<ErrorObject>().getErrorReport();
  if (!report) {
    // If the error report was missing, isMuted field can never be true,
    // and just returning false is OK.
    return false;
  }

  return report->isMuted;
}

/* static */
bool DebuggerObject::getClassName(JSContext* cx, Handle<DebuggerObject*> object,
                                  MutableHandleString result) {
  RootedObject referent(cx, object->referent());

  const char* className;
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);
    className = GetObjectClassName(cx, referent);
  }

  JSAtom* str = Atomize(cx, className, strlen(className));
  if (!str) {
    return false;
  }

  result.set(str);
  return true;
}

bool DebuggerObject::name(JSContext* cx,
                          JS::MutableHandle<JSAtom*> result) const {
  if (isFunction()) {
    JSFunction* fun = &referent()->as<JSFunction>();
    if (!fun->isAccessorWithLazyName()) {
      result.set(fun->fullExplicitName());
      if (result) {
        cx->markAtom(result);
      }
      return true;
    }

    {
      Maybe<AutoRealm> ar;
      EnterDebuggeeObjectRealm(cx, ar, fun);

      result.set(fun->getAccessorNameForLazy(cx));
      if (!result) {
        return false;
      }
    }
    cx->markAtom(result);
    return true;
  }

  MOZ_ASSERT(isBoundFunction());

  // Bound functions have a configurable `name` data property and currently
  // don't store the original name. Try a pure lookup to get this name and if
  // this fails use "bound".
  Rooted<BoundFunctionObject*> bound(cx,
                                     &referent()->as<BoundFunctionObject>());
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, bound);

    Value v;
    bool found;
    if (GetOwnPropertyPure(cx, bound, NameToId(cx->names().name), &v, &found) &&
        found && v.isString()) {
      result.set(AtomizeString(cx, v.toString()));
      if (!result) {
        return false;
      }
    } else {
      result.set(cx->names().bound);
    }
  }

  cx->markAtom(result);
  return true;
}

bool DebuggerObject::displayName(JSContext* cx,
                                 JS::MutableHandle<JSAtom*> result) const {
  if (isFunction()) {
    {
      JS::Rooted<JSFunction*> fun(cx, &referent()->as<JSFunction>());

      Maybe<AutoRealm> ar;
      EnterDebuggeeObjectRealm(cx, ar, fun);

      if (!fun->getDisplayAtom(cx, result)) {
        return false;
      }
    }
    if (result) {
      cx->markAtom(result);
    }
    return true;
  }

  MOZ_ASSERT(isBoundFunction());
  return name(cx, result);
}

JS::PromiseState DebuggerObject::promiseState() const {
  return promise()->state();
}

double DebuggerObject::promiseLifetime() const { return promise()->lifetime(); }

double DebuggerObject::promiseTimeToResolution() const {
  MOZ_ASSERT(promiseState() != JS::PromiseState::Pending);

  return promise()->timeToResolution();
}

/* static */
bool DebuggerObject::getBoundTargetFunction(
    JSContext* cx, Handle<DebuggerObject*> object,
    MutableHandle<DebuggerObject*> result) {
  MOZ_ASSERT(object->isBoundFunction());

  Rooted<BoundFunctionObject*> referent(
      cx, &object->referent()->as<BoundFunctionObject>());
  Debugger* dbg = object->owner();

  RootedObject target(cx, referent->getTarget());
  return dbg->wrapDebuggeeObject(cx, target, result);
}

/* static */
bool DebuggerObject::getBoundThis(JSContext* cx, Handle<DebuggerObject*> object,
                                  MutableHandleValue result) {
  MOZ_ASSERT(object->isBoundFunction());

  Rooted<BoundFunctionObject*> referent(
      cx, &object->referent()->as<BoundFunctionObject>());
  Debugger* dbg = object->owner();

  result.set(referent->getBoundThis());
  return dbg->wrapDebuggeeValue(cx, result);
}

/* static */
bool DebuggerObject::getBoundArguments(JSContext* cx,
                                       Handle<DebuggerObject*> object,
                                       MutableHandle<ValueVector> result) {
  MOZ_ASSERT(object->isBoundFunction());

  Rooted<BoundFunctionObject*> referent(
      cx, &object->referent()->as<BoundFunctionObject>());
  Debugger* dbg = object->owner();

  size_t length = referent->numBoundArgs();
  if (!result.resize(length)) {
    return false;
  }
  for (size_t i = 0; i < length; i++) {
    result[i].set(referent->getBoundArg(i));
    if (!dbg->wrapDebuggeeValue(cx, result[i])) {
      return false;
    }
  }
  return true;
}

/* static */
SavedFrame* Debugger::getObjectAllocationSite(JSObject& obj) {
  JSObject* metadata = GetAllocationMetadata(&obj);
  if (!metadata) {
    return nullptr;
  }

  MOZ_ASSERT(!metadata->is<WrapperObject>());
  return metadata->is<SavedFrame>() ? &metadata->as<SavedFrame>() : nullptr;
}

/* static */
bool DebuggerObject::getAllocationSite(JSContext* cx,
                                       Handle<DebuggerObject*> object,
                                       MutableHandleObject result) {
  RootedObject referent(cx, object->referent());

  RootedObject allocSite(cx, Debugger::getObjectAllocationSite(*referent));
  if (!cx->compartment()->wrap(cx, &allocSite)) {
    return false;
  }

  result.set(allocSite);
  return true;
}

/* static */
bool DebuggerObject::getErrorReport(JSContext* cx, HandleObject maybeError,
                                    JSErrorReport*& report) {
  JSObject* obj = maybeError;
  if (IsCrossCompartmentWrapper(obj)) {
    /* We only care about Error objects, so CheckedUnwrapStatic is OK. */
    obj = CheckedUnwrapStatic(obj);
  }

  if (!obj) {
    ReportAccessDenied(cx);
    return false;
  }

  if (!obj->is<ErrorObject>()) {
    report = nullptr;
    return true;
  }

  report = obj->as<ErrorObject>().getErrorReport();
  return true;
}

/* static */
bool DebuggerObject::getErrorMessageName(JSContext* cx,
                                         Handle<DebuggerObject*> object,
                                         MutableHandleString result) {
  RootedObject referent(cx, object->referent());
  JSErrorReport* report;
  if (!getErrorReport(cx, referent, report)) {
    return false;
  }

  if (!report || !report->errorMessageName) {
    result.set(nullptr);
    return true;
  }

  RootedString str(cx, JS_NewStringCopyZ(cx, report->errorMessageName));
  if (!str) {
    return false;
  }
  result.set(str);
  return true;
}

/* static */
bool DebuggerObject::getErrorNotes(JSContext* cx,
                                   Handle<DebuggerObject*> object,
                                   MutableHandleValue result) {
  RootedObject referent(cx, object->referent());
  JSErrorReport* report;
  if (!getErrorReport(cx, referent, report)) {
    return false;
  }

  if (!report) {
    result.setUndefined();
    return true;
  }

  RootedObject errorNotesArray(cx, CreateErrorNotesArray(cx, report));
  if (!errorNotesArray) {
    return false;
  }

  if (!cx->compartment()->wrap(cx, &errorNotesArray)) {
    return false;
  }
  result.setObject(*errorNotesArray);
  return true;
}

/* static */
bool DebuggerObject::getErrorLineNumber(JSContext* cx,
                                        Handle<DebuggerObject*> object,
                                        MutableHandleValue result) {
  RootedObject referent(cx, object->referent());
  JSErrorReport* report;
  if (!getErrorReport(cx, referent, report)) {
    return false;
  }

  if (!report) {
    result.setUndefined();
    return true;
  }

  result.setNumber(report->lineno);
  return true;
}

/* static */
bool DebuggerObject::getErrorColumnNumber(JSContext* cx,
                                          Handle<DebuggerObject*> object,
                                          MutableHandleValue result) {
  RootedObject referent(cx, object->referent());
  JSErrorReport* report;
  if (!getErrorReport(cx, referent, report)) {
    return false;
  }

  if (!report) {
    result.setUndefined();
    return true;
  }

  result.setNumber(report->column.oneOriginValue());
  return true;
}

/* static */
bool DebuggerObject::getPromiseValue(JSContext* cx,
                                     Handle<DebuggerObject*> object,
                                     MutableHandleValue result) {
  MOZ_ASSERT(object->promiseState() == JS::PromiseState::Fulfilled);

  result.set(object->promise()->value());
  return object->owner()->wrapDebuggeeValue(cx, result);
}

/* static */
bool DebuggerObject::getPromiseReason(JSContext* cx,
                                      Handle<DebuggerObject*> object,
                                      MutableHandleValue result) {
  MOZ_ASSERT(object->promiseState() == JS::PromiseState::Rejected);

  result.set(object->promise()->reason());
  return object->owner()->wrapDebuggeeValue(cx, result);
}

/* static */
bool DebuggerObject::isExtensible(JSContext* cx, Handle<DebuggerObject*> object,
                                  bool& result) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return IsExtensible(cx, referent, &result);
}

/* static */
bool DebuggerObject::isSealed(JSContext* cx, Handle<DebuggerObject*> object,
                              bool& result) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return TestIntegrityLevel(cx, referent, IntegrityLevel::Sealed, &result);
}

/* static */
bool DebuggerObject::isFrozen(JSContext* cx, Handle<DebuggerObject*> object,
                              bool& result) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return TestIntegrityLevel(cx, referent, IntegrityLevel::Frozen, &result);
}

/* static */
bool DebuggerObject::getPrototypeOf(JSContext* cx,
                                    Handle<DebuggerObject*> object,
                                    MutableHandle<DebuggerObject*> result) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  RootedObject proto(cx);
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);
    if (!GetPrototype(cx, referent, &proto)) {
      return false;
    }
  }

  return dbg->wrapNullableDebuggeeObject(cx, proto, result);
}

/* static */
bool DebuggerObject::getOwnPropertyNames(JSContext* cx,
                                         Handle<DebuggerObject*> object,
                                         MutableHandleIdVector result) {
  MOZ_ASSERT(result.empty());

  RootedObject referent(cx, object->referent());
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);

    ErrorCopier ec(ar);
    if (!GetPropertyKeys(cx, referent, JSITER_OWNONLY | JSITER_HIDDEN,
                         result)) {
      return false;
    }
  }

  for (size_t i = 0; i < result.length(); i++) {
    cx->markId(result[i]);
  }

  return true;
}

/* static */
bool DebuggerObject::getOwnPropertyNamesLength(JSContext* cx,
                                               Handle<DebuggerObject*> object,
                                               size_t* result) {
  RootedObject referent(cx, object->referent());

  RootedIdVector ids(cx);
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);

    ErrorCopier ec(ar);
    if (!GetPropertyKeys(cx, referent, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) {
      return false;
    }
  }

  *result = ids.length();
  return true;
}

static bool GetSymbolPropertyKeys(JSContext* cx, Handle<DebuggerObject*> object,
                                  JS::MutableHandleIdVector props,
                                  bool includePrivate) {
  RootedObject referent(cx, object->referent());

  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);

    ErrorCopier ec(ar);

    unsigned flags =
        JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY;
    if (includePrivate) {
      flags = flags | JSITER_PRIVATE;
    }
    if (!GetPropertyKeys(cx, referent, flags, props)) {
      return false;
    }
  }

  return true;
}

/* static */
bool DebuggerObject::getOwnPropertySymbols(JSContext* cx,
                                           Handle<DebuggerObject*> object,
                                           MutableHandleIdVector result) {
  MOZ_ASSERT(result.empty());

  if (!GetSymbolPropertyKeys(cx, object, result, false)) {
    return false;
  }

  for (size_t i = 0; i < result.length(); i++) {
    cx->markAtom(result[i].toSymbol());
  }

  return true;
}

/* static */
bool DebuggerObject::getOwnPrivateProperties(JSContext* cx,
                                             Handle<DebuggerObject*> object,
                                             MutableHandleIdVector result) {
  MOZ_ASSERT(result.empty());

  if (!GetSymbolPropertyKeys(cx, object, result, true)) {
    return false;
  }

  result.eraseIf([](PropertyKey key) {
    if (!key.isPrivateName()) {
      return true;
    }
    // Private *methods* create a Private Brand, a special private name
    // stamped onto the symbol, to indicate it is possible to execute private
    // methods from the class on this object. We don't want to return such
    // items here, so we check if we're dealing with a private property, e.g.
    // the Symbol description starts with a "#" character.
    JSAtom* privateDescription = key.toSymbol()->description();
    if (privateDescription->length() == 0) {
      return true;
    }
    char16_t firstChar = privateDescription->latin1OrTwoByteChar(0);
    return firstChar != '#';
  });

  for (size_t i = 0; i < result.length(); i++) {
    cx->markAtom(result[i].toSymbol());
  }

  return true;
}

/* static */
bool DebuggerObject::getOwnPropertyDescriptor(
    JSContext* cx, Handle<DebuggerObject*> object, HandleId id,
    MutableHandle<Maybe<PropertyDescriptor>> desc_) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  // Bug: This can cause the debuggee to run!
  {
    Maybe<AutoRealm> ar;
    EnterDebuggeeObjectRealm(cx, ar, referent);

    cx->markId(id);

    ErrorCopier ec(ar);
    if (!GetOwnPropertyDescriptor(cx, referent, id, desc_)) {
      return false;
    }
  }

  if (desc_.isSome()) {
    Rooted<PropertyDescriptor> desc(cx, *desc_);

    if (desc.hasValue()) {
      // Rewrap the debuggee values in desc for the debugger.
      if (!dbg->wrapDebuggeeValue(cx, desc.value())) {
        return false;
      }
    }
    if (desc.hasGetter()) {
      RootedValue get(cx, ObjectOrNullValue(desc.getter()));
      if (!dbg->wrapDebuggeeValue(cx, &get)) {
        return false;
      }
      desc.setGetter(get.toObjectOrNull());
    }
    if (desc.hasSetter()) {
      RootedValue set(cx, ObjectOrNullValue(desc.setter()));
      if (!dbg->wrapDebuggeeValue(cx, &set)) {
        return false;
      }
      desc.setSetter(set.toObjectOrNull());
    }

    desc_.set(mozilla::Some(desc.get()));
  }

  return true;
}

/* static */
bool DebuggerObject::preventExtensions(JSContext* cx,
                                       Handle<DebuggerObject*> object) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return PreventExtensions(cx, referent);
}

/* static */
bool DebuggerObject::seal(JSContext* cx, Handle<DebuggerObject*> object) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return SetIntegrityLevel(cx, referent, IntegrityLevel::Sealed);
}

/* static */
bool DebuggerObject::freeze(JSContext* cx, Handle<DebuggerObject*> object) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  ErrorCopier ec(ar);
  return SetIntegrityLevel(cx, referent, IntegrityLevel::Frozen);
}

/* static */
bool DebuggerObject::defineProperty(JSContext* cx,
                                    Handle<DebuggerObject*> object, HandleId id,
                                    Handle<PropertyDescriptor> desc_) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  Rooted<PropertyDescriptor> desc(cx, desc_);
  if (!dbg->unwrapPropertyDescriptor(cx, referent, &desc)) {
    return false;
  }
  JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, desc));

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  if (!cx->compartment()->wrap(cx, &desc)) {
    return false;
  }
  cx->markId(id);

  ErrorCopier ec(ar);
  return DefineProperty(cx, referent, id, desc);
}

/* static */
bool DebuggerObject::defineProperties(JSContext* cx,
                                      Handle<DebuggerObject*> object,
                                      Handle<IdVector> ids,
                                      Handle<PropertyDescriptorVector> descs_) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  Rooted<PropertyDescriptorVector> descs(cx, PropertyDescriptorVector(cx));
  if (!descs.append(descs_.begin(), descs_.end())) {
    return false;
  }
  for (size_t i = 0; i < descs.length(); i++) {
    if (!dbg->unwrapPropertyDescriptor(cx, referent, descs[i])) {
      return false;
    }
    JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, descs[i]));
  }

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  for (size_t i = 0; i < descs.length(); i++) {
    if (!cx->compartment()->wrap(cx, descs[i])) {
      return false;
    }
    cx->markId(ids[i]);
  }

  ErrorCopier ec(ar);
  for (size_t i = 0; i < descs.length(); i++) {
    if (!DefineProperty(cx, referent, ids[i], descs[i])) {
      return false;
    }
  }

  return true;
}

/* static */
bool DebuggerObject::deleteProperty(JSContext* cx,
                                    Handle<DebuggerObject*> object, HandleId id,
                                    ObjectOpResult& result) {
  RootedObject referent(cx, object->referent());

  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  cx->markId(id);

  ErrorCopier ec(ar);
  return DeleteProperty(cx, referent, id, result);
}

/* static */
Result<Completion> DebuggerObject::getProperty(JSContext* cx,
                                               Handle<DebuggerObject*> object,
                                               HandleId id,
                                               HandleValue receiver_) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  // Unwrap Debugger.Objects. This happens in the debugger's compartment since
  // that is where any exceptions must be reported.
  RootedValue receiver(cx, receiver_);
  if (!dbg->unwrapDebuggeeValue(cx, &receiver)) {
    return cx->alreadyReportedError();
  }

  // Enter the debuggee compartment and rewrap all input value for that
  // compartment. (Rewrapping always takes place in the destination
  // compartment.)
  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);
  if (!cx->compartment()->wrap(cx, &referent) ||
      !cx->compartment()->wrap(cx, &receiver)) {
    return cx->alreadyReportedError();
  }
  cx->markId(id);

  LeaveDebuggeeNoExecute nnx(cx);

  RootedValue result(cx);
  bool ok = GetProperty(cx, referent, receiver, id, &result);
  return Completion::fromJSResult(cx, ok, result);
}

/* static */
Result<Completion> DebuggerObject::setProperty(JSContext* cx,
                                               Handle<DebuggerObject*> object,
                                               HandleId id, HandleValue value_,
                                               HandleValue receiver_) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  // Unwrap Debugger.Objects. This happens in the debugger's compartment since
  // that is where any exceptions must be reported.
  RootedValue value(cx, value_);
  RootedValue receiver(cx, receiver_);
  if (!dbg->unwrapDebuggeeValue(cx, &value) ||
      !dbg->unwrapDebuggeeValue(cx, &receiver)) {
    return cx->alreadyReportedError();
  }

  // Enter the debuggee compartment and rewrap all input value for that
  // compartment. (Rewrapping always takes place in the destination
  // compartment.)
  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);
  if (!cx->compartment()->wrap(cx, &referent) ||
      !cx->compartment()->wrap(cx, &value) ||
      !cx->compartment()->wrap(cx, &receiver)) {
    return cx->alreadyReportedError();
  }
  cx->markId(id);

  LeaveDebuggeeNoExecute nnx(cx);

  ObjectOpResult opResult;
  bool ok = SetProperty(cx, referent, id, value, receiver, opResult);

  return Completion::fromJSResult(cx, ok, BooleanValue(ok && opResult.ok()));
}

/* static */
Maybe<Completion> DebuggerObject::call(JSContext* cx,
                                       Handle<DebuggerObject*> object,
                                       HandleValue thisv_,
                                       Handle<ValueVector> args) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  if (!referent->isCallable()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_INCOMPATIBLE_PROTO, "Debugger.Object",
                              "call", referent->getClass()->name);
    return Nothing();
  }

  RootedValue calleev(cx, ObjectValue(*referent));

  // Unwrap Debugger.Objects. This happens in the debugger's compartment since
  // that is where any exceptions must be reported.
  RootedValue thisv(cx, thisv_);
  if (!dbg->unwrapDebuggeeValue(cx, &thisv)) {
    return Nothing();
  }
  Rooted<ValueVector> args2(cx, ValueVector(cx));
  if (!args2.append(args.begin(), args.end())) {
    return Nothing();
  }
  for (size_t i = 0; i < args2.length(); ++i) {
    if (!dbg->unwrapDebuggeeValue(cx, args2[i])) {
      return Nothing();
    }
  }

  // Enter the debuggee compartment and rewrap all input value for that
  // compartment. (Rewrapping always takes place in the destination
  // compartment.)
  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);
  if (!cx->compartment()->wrap(cx, &calleev) ||
      !cx->compartment()->wrap(cx, &thisv)) {
    return Nothing();
  }
  for (size_t i = 0; i < args2.length(); ++i) {
    if (!cx->compartment()->wrap(cx, args2[i])) {
      return Nothing();
    }
  }

  // Note whether we are in an evaluation that might invoke the OnNativeCall
  // hook, so that the JITs will be disabled.
  Maybe<AutoNoteExclusiveDebuggerOnEval> noteEvaluation;
  if (dbg->isExclusiveDebuggerOnEval()) {
    noteEvaluation.emplace(cx, dbg);
  }

  // Call the function.
  LeaveDebuggeeNoExecute nnx(cx);

  RootedValue result(cx);
  bool ok;
  {
    InvokeArgs invokeArgs(cx);

    ok = invokeArgs.init(cx, args2.length());
    if (ok) {
      for (size_t i = 0; i < args2.length(); ++i) {
        invokeArgs[i].set(args2[i]);
      }

      ok = js::Call(cx, calleev, thisv, invokeArgs, &result);
    }
  }

  Rooted<Completion> completion(cx, Completion::fromJSResult(cx, ok, result));
  ar.reset();
  return Some(std::move(completion.get()));
}

/* static */
bool DebuggerObject::forceLexicalInitializationByName(
    JSContext* cx, Handle<DebuggerObject*> object, HandleId id, bool& result) {
  if (!id.isString()) {
    JS_ReportErrorNumberASCII(
        cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
        "Debugger.Object.prototype.forceLexicalInitializationByName", "string",
        InformalValueTypeName(IdToValue(id)));
    return false;
  }

  MOZ_ASSERT(object->isGlobal());

  Rooted<GlobalObject*> referent(cx, &object->referent()->as<GlobalObject>());

  // Shape::search can end up allocating a new BaseShape in Shape::cachify so
  // we need to be in the right compartment here.
  Maybe<AutoRealm> ar;
  EnterDebuggeeObjectRealm(cx, ar, referent);

  RootedObject globalLexical(cx, &referent->lexicalEnvironment());
  RootedObject pobj(cx);
  PropertyResult prop;
  if (!LookupProperty(cx, globalLexical, id, &pobj, &prop)) {
    return false;
  }

  result = false;
  if (prop.isFound()) {
    MOZ_ASSERT(prop.isNativeProperty());
    PropertyInfo propInfo = prop.propertyInfo();
    Value v = globalLexical->as<NativeObject>().getSlot(propInfo.slot());
    if (propInfo.isDataProperty() && v.isMagic() &&
        v.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
      globalLexical->as<NativeObject>().setSlot(propInfo.slot(),
                                                UndefinedValue());
      result = true;
    }
  }

  return true;
}

/* static */
Result<Completion> DebuggerObject::executeInGlobal(
    JSContext* cx, Handle<DebuggerObject*> object,
    mozilla::Range<const char16_t> chars, HandleObject bindings,
    const EvalOptions& options) {
  MOZ_ASSERT(object->isGlobal());

  Rooted<GlobalObject*> referent(cx, &object->referent()->as<GlobalObject>());
  Debugger* dbg = object->owner();

  RootedObject globalLexical(cx, &referent->lexicalEnvironment());
  return DebuggerGenericEval(cx, chars, bindings, options, dbg, globalLexical,
                             nullptr);
}

/* static */
bool DebuggerObject::makeDebuggeeValue(JSContext* cx,
                                       Handle<DebuggerObject*> object,
                                       HandleValue value_,
                                       MutableHandleValue result) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  RootedValue value(cx, value_);

  // Non-objects are already debuggee values.
  if (value.isObject()) {
    // Enter this Debugger.Object's referent's compartment, and wrap the
    // argument as appropriate for references from there.
    {
      Maybe<AutoRealm> ar;
      EnterDebuggeeObjectRealm(cx, ar, referent);
      if (!cx->compartment()->wrap(cx, &value)) {
        return false;
      }
    }

    // Back in the debugger's compartment, produce a new Debugger.Object
    // instance referring to the wrapped argument.
    if (!dbg->wrapDebuggeeValue(cx, &value)) {
      return false;
    }
  }

  result.set(value);
  return true;
}

static JSFunction* EnsureNativeFunction(const Value& value) {
  if (!value.isObject() || !value.toObject().is<JSFunction>()) {
    return nullptr;
  }

  JSFunction* fun = &value.toObject().as<JSFunction>();
  if (!fun->isNativeFun()) {
    return nullptr;
  }

  return fun;
}

static JSAtom* MaybeGetSelfHostedFunctionName(const Value& v) {
  if (!v.isObject() || !v.toObject().is<JSFunction>()) {
    return nullptr;
  }

  JSFunction* fun = &v.toObject().as<JSFunction>();
  if (!fun->isSelfHostedBuiltin()) {
    return nullptr;
  }

  return GetClonedSelfHostedFunctionName(fun);
}

static bool IsSameNative(JSFunction* a, JSFunction* b,
                         DebuggerObject::CheckJitInfo checkJitInfo) {
  if (a->native() != b->native()) {
    return false;
  }

  if (checkJitInfo == DebuggerObject::CheckJitInfo::No) {
    return true;
  }

  // Both function should agree with the existence of JitInfo.

  if (a->hasJitInfo() != b->hasJitInfo()) {
    return false;
  }

  if (!a->hasJitInfo()) {
    return true;
  }

  if (a->jitInfo() == b->jitInfo()) {
    return true;
  }

  return false;
}

/* static */
bool DebuggerObject::isSameNative(JSContext* cx, Handle<DebuggerObject*> object,
                                  HandleValue value, CheckJitInfo checkJitInfo,
                                  MutableHandleValue result) {
  RootedValue referentValue(cx, ObjectValue(*object->referent()));

  RootedValue nonCCWValue(
      cx, value.isObject() ? ObjectValue(*UncheckedUnwrap(&value.toObject()))
                           : value);

  RootedFunction fun(cx, EnsureNativeFunction(nonCCWValue));
  if (!fun) {
    Rooted<JSAtom*> selfHostedName(cx,
                                   MaybeGetSelfHostedFunctionName(nonCCWValue));
    if (!selfHostedName) {
      JS_ReportErrorASCII(cx, "Need native function");
      return false;
    }

    result.setBoolean(selfHostedName ==
                      MaybeGetSelfHostedFunctionName(referentValue));
    return true;
  }

  RootedFunction referentFun(cx, EnsureNativeFunction(referentValue));

  result.setBoolean(referentFun &&
                    IsSameNative(referentFun, fun, checkJitInfo));
  return true;
}

static bool IsNativeGetterWithJitInfo(JSFunction* fun) {
  return fun->isNativeFun() && fun->hasJitInfo() &&
         fun->jitInfo()->type() == JSJitInfo::Getter;
}

/* static */
bool DebuggerObject::isNativeGetterWithJitInfo(JSContext* cx,
                                               Handle<DebuggerObject*> object,
                                               MutableHandleValue result) {
  RootedValue referentValue(cx, ObjectValue(*object->referent()));
  RootedFunction referentFun(cx, EnsureNativeFunction(referentValue));
  result.setBoolean(referentFun && IsNativeGetterWithJitInfo(referentFun));
  return true;
}

/* static */
bool DebuggerObject::unsafeDereference(JSContext* cx,
                                       Handle<DebuggerObject*> object,
                                       MutableHandleObject result) {
  RootedObject referent(cx, object->referent());

  if (!cx->compartment()->wrap(cx, &referent)) {
    return false;
  }

  // Wrapping should return the WindowProxy.
  MOZ_ASSERT(!IsWindow(referent));

  result.set(referent);
  return true;
}

/* static */
bool DebuggerObject::unwrap(JSContext* cx, Handle<DebuggerObject*> object,
                            MutableHandle<DebuggerObject*> result) {
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();

  RootedObject unwrapped(cx, UnwrapOneCheckedStatic(referent));

  // Don't allow unwrapping to create a D.O whose referent is in an
  // invisible-to-Debugger compartment. (If our referent is a *wrapper* to such,
  // and the wrapper is in a visible compartment, that's fine.)
  if (unwrapped && unwrapped->compartment()->invisibleToDebugger()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
    return false;
  }

  return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result);
}

/* static */
bool DebuggerObject::requireGlobal(JSContext* cx,
                                   Handle<DebuggerObject*> object) {
  if (!object->isGlobal()) {
    RootedObject referent(cx, object->referent());

    const char* isWrapper = "";
    const char* isWindowProxy = "";

    // Help the poor programmer by pointing out wrappers around globals...
    if (referent->is<WrapperObject>()) {
      referent = js::UncheckedUnwrap(referent);
      isWrapper = "a wrapper around ";
    }

    // ... and WindowProxies around Windows.
    if (IsWindowProxy(referent)) {
      referent = ToWindowIfWindowProxy(referent);
      isWindowProxy = "a WindowProxy referring to ";
    }

    RootedValue dbgobj(cx, ObjectValue(*object));
    if (referent->is<GlobalObject>()) {
      ReportValueError(cx, JSMSG_DEBUG_WRAPPER_IN_WAY, JSDVG_SEARCH_STACK,
                       dbgobj, nullptr, isWrapper, isWindowProxy);
    } else {
      ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, dbgobj,
                       nullptr, "a global object");
    }
    return false;
  }

  return true;
}

/* static */
bool DebuggerObject::requirePromise(JSContext* cx,
                                    Handle<DebuggerObject*> object) {
  RootedObject referent(cx, object->referent());

  if (IsCrossCompartmentWrapper(referent)) {
    /* We only care about promises, so CheckedUnwrapStatic is OK. */
    referent = CheckedUnwrapStatic(referent);
    if (!referent) {
      ReportAccessDenied(cx);
      return false;
    }
  }

  if (!referent->is<PromiseObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_EXPECTED_TYPE, "Debugger", "Promise",
                              object->getClass()->name);
    return false;
  }

  return true;
}

/* static */
bool DebuggerObject::getScriptedProxyTarget(
    JSContext* cx, Handle<DebuggerObject*> object,
    MutableHandle<DebuggerObject*> result) {
  MOZ_ASSERT(object->isScriptedProxy());
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();
  RootedObject unwrapped(cx, js::GetProxyTargetObject(referent));

  return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result);
}

/* static */
bool DebuggerObject::getScriptedProxyHandler(
    JSContext* cx, Handle<DebuggerObject*> object,
    MutableHandle<DebuggerObject*> result) {
  MOZ_ASSERT(object->isScriptedProxy());
  RootedObject referent(cx, object->referent());
  Debugger* dbg = object->owner();
  RootedObject unwrapped(cx, ScriptedProxyHandler::handlerObject(referent));
  return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result);
}
