/* 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 "ChromeUtils.h"

#include "JSOracleParent.h"
#include "ThirdPartyUtil.h"
#include "VsyncSource.h"
#include "WrapperFactory.h"
#include "imgLoader.h"
#include "js/CallAndConstruct.h"  // JS::Call
#include "js/CharacterEncoding.h"
#include "js/ColumnNumber.h"  // JS::TaggedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin
#include "js/Date.h"  // JS::IsISOStyleDate
#include "js/JSON.h"
#include "js/Object.h"              // JS::GetClass
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_SetProperty, JS_SetPropertyById, JS::IdVector
#include "js/PropertyDescriptor.h"  // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
#include "js/SavedFrameAPI.h"
#include "js/Value.h"  // JS::Value, JS::StringValue
#include "jsfriendapi.h"
#include "mozJSModuleLoader.h"
#include "mozilla/Base64.h"
#include "mozilla/Components.h"
#include "mozilla/ControllerCommand.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/FormAutofillNative.h"
#include "mozilla/IntentionalCrash.h"
#include "mozilla/KeySystemConfig.h"
#include "mozilla/PerfStats.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProcInfo.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/RemoteMediaManagerChild.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollingMetrics.h"
#include "mozilla/SharedStyleSheetCache.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/WheelHandlingHelper.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/IdleDeadline.h"
#include "mozilla/dom/InProcessParent.h"
#include "mozilla/dom/JSActorService.h"
#include "mozilla/dom/MediaSessionBinding.h"
#include "mozilla/dom/PBrowserParent.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Record.h"
#include "mozilla/dom/ReportingHeader.h"
#include "mozilla/dom/SharedScriptCache.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/WindowBinding.h"  // For IdleRequestCallback/Options
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/image/FetchDecodedImage.h"
#include "mozilla/ipc/GeckoChildProcessHost.h"
#include "mozilla/ipc/UtilityProcessHost.h"
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessSandboxing.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/net/ChannelClassifierUtils.h"
#include "nsContentUtils.h"
#include "nsControllerCommandTable.h"
#include "nsDocShell.h"
#include "nsIException.h"
#include "nsIRFPTargetSetIDL.h"
#include "nsIURIFixup.h"
#include "nsIWidget.h"
#include "nsNativeTheme.h"
#include "nsPIDOMWindowInlines.h"
#include "nsRFPTargetSetIDL.h"
#include "nsString.h"
#include "nsThreadUtils.h"

#ifdef XP_UNIX
#  include <errno.h>
#  include <fcntl.h>
#  include <poll.h>
#  include <sys/wait.h>
#  include <unistd.h>

#  ifdef XP_LINUX
#    include <sys/prctl.h>
#  endif
#endif

#ifdef MOZ_WMF_CDM
#  include "mozilla/MFCDMParent.h"
#endif

#ifdef MOZ_WIDGET_ANDROID
#  include "mozilla/java/GeckoAppShellWrappers.h"
#endif

#ifdef MOZ_WAYLAND
#  include "mozilla/WidgetUtilsGtk.h"
#endif

namespace mozilla::dom {

// Setup logging
extern mozilla::LazyLogModule gMlsLog;

/* static */
void ChromeUtils::NondeterministicGetWeakMapKeys(
    GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
    JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
  if (!aMap.isObject()) {
    aRetval.setUndefined();
  } else {
    JSContext* cx = aGlobal.Context();
    JS::Rooted<JSObject*> objRet(cx);
    JS::Rooted<JSObject*> mapObj(cx, &aMap.toObject());
    if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &objRet)) {
      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    } else {
      aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
    }
  }
}

/* static */
void ChromeUtils::NondeterministicGetWeakSetKeys(
    GlobalObject& aGlobal, JS::Handle<JS::Value> aSet,
    JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
  if (!aSet.isObject()) {
    aRetval.setUndefined();
  } else {
    JSContext* cx = aGlobal.Context();
    JS::Rooted<JSObject*> objRet(cx);
    JS::Rooted<JSObject*> setObj(cx, &aSet.toObject());
    if (!JS_NondeterministicGetWeakSetKeys(cx, setObj, &objRet)) {
      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    } else {
      aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
    }
  }
}

/* static */
void ChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
                                  const ArrayBufferViewOrArrayBuffer& aSource,
                                  const Base64URLEncodeOptions& aOptions,
                                  nsACString& aResult, ErrorResult& aRv) {
  auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include
                                     : Base64URLEncodePaddingPolicy::Omit;
  ProcessTypedArrays(
      aSource, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
        nsresult rv = mozilla::Base64URLEncode(aData.Length(), aData.Elements(),
                                               paddingPolicy, aResult);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          aResult.Truncate();
          aRv.Throw(rv);
        }
      });
}

/* static */
void ChromeUtils::Base64URLDecode(GlobalObject& aGlobal,
                                  const nsACString& aString,
                                  const Base64URLDecodeOptions& aOptions,
                                  JS::MutableHandle<JSObject*> aRetval,
                                  ErrorResult& aRv) {
  Base64URLDecodePaddingPolicy paddingPolicy;
  switch (aOptions.mPadding) {
    case Base64URLDecodePadding::Require:
      paddingPolicy = Base64URLDecodePaddingPolicy::Require;
      break;

    case Base64URLDecodePadding::Ignore:
      paddingPolicy = Base64URLDecodePaddingPolicy::Ignore;
      break;

    case Base64URLDecodePadding::Reject:
      paddingPolicy = Base64URLDecodePaddingPolicy::Reject;
      break;

    default:
      aRv.Throw(NS_ERROR_INVALID_ARG);
      return;
  }
  FallibleTArray<uint8_t> data;
  nsresult rv = mozilla::Base64URLDecode(aString, paddingPolicy, data);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRv.Throw(rv);
    return;
  }

  JS::Rooted<JSObject*> buffer(
      aGlobal.Context(), ArrayBuffer::Create(aGlobal.Context(), data, aRv));
  if (aRv.Failed()) {
    return;
  }
  aRetval.set(buffer);
}

/* static */
void ChromeUtils::ReleaseAssert(GlobalObject& aGlobal, bool aCondition,
                                const nsAString& aMessage) {
  // If the condition didn't fail, which is the likely case, immediately return.
  if (MOZ_LIKELY(aCondition)) {
    return;
  }

  // Extract the current stack from the JS runtime to embed in the crash reason.
  nsAutoCString filename;
  uint32_t lineNo = 0;

  if (nsCOMPtr<nsIStackFrame> location = GetCurrentJSStack(1)) {
    location->GetFilename(aGlobal.Context(), filename);
    lineNo = location->GetLineNumber(aGlobal.Context());
  } else {
    filename.Assign("<unknown>"_ns);
  }

  // Convert to utf-8 for adding as the MozCrashReason.
  NS_ConvertUTF16toUTF8 messageUtf8(aMessage);

  // Actually crash.
  MOZ_CRASH_UNSAFE_PRINTF("Failed ChromeUtils.releaseAssert(\"%s\") @ %s:%u",
                          messageUtf8.get(), filename.get(), lineNo);
}

/* static */
void ChromeUtils::RegisterMarkerSchema(GlobalObject& aGlobal,
                                       JS::Handle<JSObject*> aSchema,
                                       ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();
  JS::Rooted<JSObject*> schemaObj(cx, aSchema);

  JS::Rooted<JS::Value> nameVal(cx);
  if (!JS_GetProperty(cx, schemaObj, "name", &nameVal) || !nameVal.isString()) {
    aRv.ThrowTypeError("Schema must contain a 'name' field (string)");
    return;
  }

  JS::Rooted<JSString*> nameStr(cx, nameVal.toString());
  nsAutoJSString schemaName;
  if (!schemaName.init(cx, nameStr)) {
    aRv.ThrowTypeError("Failed to extract schema name");
    return;
  }

  nsString jsonString;

  auto callback = [](const char16_t* buf, uint32_t len, void* data) {
    static_cast<nsString*>(data)->Append(buf, len);
    return true;
  };

  if (!JS::ToJSONMaybeSafely(cx, schemaObj, callback, &jsonString)) {
    aRv.ThrowTypeError("Failed to serialize schema");
    return;
  }

  NS_ConvertUTF16toUTF8 schemaNameUTF8(schemaName);
  profiler_register_marker_schema(schemaNameUTF8, jsonString);
}

// Wrapper marker type for custom markers from JavaScript.
// Uses an empty name for two reasons:
// 1. Avoids writing a "type" field in marker data (the user's type wins)
// 2. Prevents this wrapper's schema from appearing in profile.meta.markerSchema
//    (empty-name markers are filtered out during schema streaming)
struct JSCustomMarker : public ::mozilla::BaseMarkerType<JSCustomMarker> {
  static constexpr const char* Name = "";
  static constexpr bool StoreName = true;

  using MS = ::mozilla::MarkerSchema;

  static constexpr MS::PayloadField PayloadFields[] = {};
  static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
                                               MS::Location::MarkerTable};

  static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
                                   const ProfilerString8View& aJSON) {
    // Splice the user's JSON properties directly into the marker data.
    // By setting Name = "" above, we avoid having the profiler infrastructure
    // write its own "type" field, so the only "type" field comes from the
    // user's data object (e.g., "CustomMarker").
    auto stringView = aJSON.StringView();
    const char* data = stringView.data();
    size_t length = stringView.length();

    if (length >= 2 && data[0] == '{' && data[length - 1] == '}') {
      // Skip the opening '{' and closing '}'
      aWriter.Splice(data + 1, length - 2);
    }
  }
};

/* static */
void ChromeUtils::AddProfilerMarker(
    GlobalObject& aGlobal, const nsACString& aName,
    const ProfilerMarkerOptionsOrDouble& aOptions,
    const Optional<UTF8StringOrObject>& aData) {
  if (!profiler_thread_is_being_profiled_for_markers()) {
    return;
  }

  MarkerOptions options;

  MarkerCategory category = ::geckoprofiler::category::JS;

  DOMHighResTimeStamp startTime = 0;
  uint64_t innerWindowId = 0;
  if (aOptions.IsDouble()) {
    startTime = aOptions.GetAsDouble();
  } else {
    const ProfilerMarkerOptions& opt = aOptions.GetAsProfilerMarkerOptions();
    startTime = opt.mStartTime;
    innerWindowId = opt.mInnerWindowId;

    if (opt.mCaptureStack) {
      // If we will be capturing a stack, change the category of the
      // ChromeUtils.addProfilerMarker label automatically added by the webidl
      // binding from DOM to PROFILER so that this function doesn't appear in
      // the marker stack.
      JSContext* cx = aGlobal.Context();
      ProfilingStack* stack = js::GetContextProfilingStackIfEnabled(cx);
      if (MOZ_LIKELY(stack)) {
        uint32_t sp = stack->stackPointer;
        if (MOZ_LIKELY(sp > 0)) {
          js::ProfilingStackFrame& frame = stack->frames[sp - 1];
          if (frame.isLabelFrame() && "ChromeUtils"_ns.Equals(frame.label()) &&
              "addProfilerMarker"_ns.Equals(frame.dynamicString())) {
            frame.setLabelCategory(JS::ProfilingCategoryPair::PROFILER);
          }
        }
      }

      options.Set(MarkerStack::Capture());
    }
#define BEGIN_CATEGORY(name, labelAsString, color) \
  if (opt.mCategory.Equals(labelAsString)) {       \
    category = ::geckoprofiler::category::name;    \
  } else
#define SUBCATEGORY(supercategory, name, labelAsString)
#define END_CATEGORY
    MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY)
#undef BEGIN_CATEGORY
#undef SUBCATEGORY
#undef END_CATEGORY
    {
      category = ::geckoprofiler::category::OTHER;
    }
  }
  if (startTime) {
    options.Set(MarkerTiming::IntervalUntilNowFrom(
        TimeStamp::ProcessCreation() +
        TimeDuration::FromMilliseconds(startTime)));
  }

  if (innerWindowId) {
    options.Set(MarkerInnerWindowId(innerWindowId));
  } else {
    options.Set(MarkerInnerWindowIdFromJSContext(aGlobal.Context()));
  }

  {
    AUTO_PROFILER_STATS(ChromeUtils_AddProfilerMarker);
    if (aData.WasPassed()) {
      const auto& data = aData.Value();

      if (data.IsUTF8String()) {
        profiler_add_marker(aName, category, std::move(options),
                            ::geckoprofiler::markers::TextMarker{},
                            data.GetAsUTF8String());
      } else {
        JSContext* cx = aGlobal.Context();
        JS::Rooted<JSObject*> obj(cx, data.GetAsObject());

        // JS::ToJSONMaybeSafely asserts that its input is a plain object or
        // array; a wrapper would trip that assertion. Unwrap and test the
        // underlying object directly.
        JS::Rooted<JSObject*> unwrapped(cx, js::CheckedUnwrapStatic(obj));

        if (!unwrapped || !JS::IsPlainObject(unwrapped)) {
          // Non-plain object (e.g. an Error) or denied unwrap: fall back to a
          // text marker using the object's string form.
          JS::Rooted<JS::Value> objValue(cx, JS::ObjectValue(*obj));
          JS::Rooted<JSString*> str(cx, JS::ToString(cx, objValue));
          nsAutoCString text;
          if (!str) {
            JS_ClearPendingException(cx);
            text.AssignLiteral("<error converting to string>");
          } else {
            nsAutoJSString jsText;
            NS_ENSURE_TRUE_VOID(jsText.init(cx, str));
            CopyUTF16toUTF8(jsText, text);
          }
          profiler_add_marker(aName, category, std::move(options),
                              ::geckoprofiler::markers::TextMarker{}, text);
          return;
        }

        nsString jsonString;
        auto callback = [](const char16_t* buf, uint32_t len, void* d) {
          static_cast<nsString*>(d)->Append(buf, len);
          return true;
        };

        // ToJSONMaybeSafely requires same-realm input; enter the unwrapped
        // object's realm before calling it.
        {
          JSAutoRealm ar(cx, unwrapped);
          if (!JS::ToJSONMaybeSafely(cx, unwrapped, callback, &jsonString)) {
            JS_ClearPendingException(cx);
            return;
          }
        }

        NS_ConvertUTF16toUTF8 jsonUTF8(jsonString);

        profiler_add_marker(
            aName, category, std::move(options), JSCustomMarker{},
            ProfilerString8View::WrapNullTerminatedString(jsonUTF8.get()));
      }
    } else {
      profiler_add_marker(aName, category, std::move(options));
    }
  }
}

/* static */
void ChromeUtils::GetXPCOMErrorName(GlobalObject& aGlobal, uint32_t aErrorCode,
                                    nsACString& aRetval) {
  GetErrorName((nsresult)aErrorCode, aRetval);
}

/* static */
void ChromeUtils::WaiveXrays(GlobalObject& aGlobal, JS::Handle<JS::Value> aVal,
                             JS::MutableHandle<JS::Value> aRetval,
                             ErrorResult& aRv) {
  JS::Rooted<JS::Value> value(aGlobal.Context(), aVal);
  if (!xpc::WrapperFactory::WaiveXrayAndWrap(aGlobal.Context(), &value)) {
    aRv.NoteJSContextException(aGlobal.Context());
  } else {
    aRetval.set(value);
  }
}

/* static */
void ChromeUtils::UnwaiveXrays(GlobalObject& aGlobal,
                               JS::Handle<JS::Value> aVal,
                               JS::MutableHandle<JS::Value> aRetval,
                               ErrorResult& aRv) {
  if (!aVal.isObject()) {
    aRetval.set(aVal);
    return;
  }

  JS::Rooted<JSObject*> obj(aGlobal.Context(),
                            js::UncheckedUnwrap(&aVal.toObject()));
  if (!JS_WrapObject(aGlobal.Context(), &obj)) {
    aRv.NoteJSContextException(aGlobal.Context());
  } else {
    aRetval.setObject(*obj);
  }
}

/* static */
void ChromeUtils::GetClassName(GlobalObject& aGlobal,
                               JS::Handle<JSObject*> aObj, bool aUnwrap,
                               nsAString& aRetval) {
  JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
  if (aUnwrap) {
    obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
  }

  aRetval = NS_ConvertUTF8toUTF16(nsDependentCString(JS::GetClass(obj)->name));
}

/* static */
bool ChromeUtils::IsDOMObject(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
                              bool aUnwrap) {
  JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
  if (aUnwrap) {
    obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
  }

  return mozilla::dom::IsDOMObject(obj);
}

/* static */
bool ChromeUtils::IsISOStyleDate(GlobalObject& aGlobal,
                                 const nsACString& aStr) {
  // aStr is a UTF-8 string, however we can cast to JS::Latin1Chars
  // because JS::IsISOStyleDate handles ASCII only
  return JS::IsISOStyleDate(aGlobal.Context(),
                            JS::Latin1Chars(aStr.Data(), aStr.Length()));
}

/* static */
void ChromeUtils::ShallowClone(GlobalObject& aGlobal,
                               JS::Handle<JSObject*> aObj,
                               JS::Handle<JSObject*> aTarget,
                               JS::MutableHandle<JSObject*> aRetval,
                               ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();

  auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });

  JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
  JS::RootedVector<JS::Value> values(cx);
  JS::RootedVector<jsid> valuesIds(cx);

  {
    // cx represents our current Realm, so it makes sense to use it for the
    // CheckedUnwrapDynamic call.  We do want CheckedUnwrapDynamic, in case
    // someone is shallow-cloning a Window.
    JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapDynamic(aObj, cx));
    if (!obj) {
      js::ReportAccessDenied(cx);
      return;
    }

    if (js::IsScriptedProxy(obj)) {
      JS_ReportErrorASCII(cx, "Shallow cloning a proxy object is not allowed");
      return;
    }

    JSAutoRealm ar(cx, obj);

    if (!JS_Enumerate(cx, obj, &ids) || !values.reserve(ids.length()) ||
        !valuesIds.reserve(ids.length())) {
      return;
    }

    JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
    JS::Rooted<JS::PropertyKey> id(cx);
    for (jsid idVal : ids) {
      id = idVal;
      if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) {
        continue;
      }
      if (desc.isNothing() || desc->isAccessorDescriptor()) {
        continue;
      }
      valuesIds.infallibleAppend(id);
      values.infallibleAppend(desc->value());
    }
  }

  JS::Rooted<JSObject*> obj(cx);
  {
    Maybe<JSAutoRealm> ar;
    if (aTarget) {
      // Our target could be anything, so we want CheckedUnwrapDynamic here.
      // "cx" represents the current Realm when we were called from bindings, so
      // we can just use that.
      JS::Rooted<JSObject*> target(cx, js::CheckedUnwrapDynamic(aTarget, cx));
      if (!target) {
        js::ReportAccessDenied(cx);
        return;
      }
      ar.emplace(cx, target);
    }

    obj = JS_NewPlainObject(cx);
    if (!obj) {
      return;
    }

    JS::Rooted<JS::Value> value(cx);
    JS::Rooted<JS::PropertyKey> id(cx);
    for (uint32_t i = 0; i < valuesIds.length(); i++) {
      id = valuesIds[i];
      value = values[i];

      JS_MarkCrossZoneId(cx, id);
      if (!JS_WrapValue(cx, &value) ||
          !JS_SetPropertyById(cx, obj, id, value)) {
        return;
      }
    }
  }

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

  cleanup.release();
  aRetval.set(obj);
}

namespace {
class IdleDispatchRunnable final : public IdleRunnable,
                                   public nsITimerCallback {
 public:
  NS_DECL_ISUPPORTS_INHERITED

  IdleDispatchRunnable(nsIGlobalObject* aParent, IdleRequestCallback& aCallback)
      : IdleRunnable("ChromeUtils::IdleDispatch"),
        mCallback(&aCallback),
        mParent(aParent) {}

  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
  // See bug 1535398.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
    if (mCallback) {
      CancelTimer();

      auto deadline = mDeadline - TimeStamp::ProcessCreation();

      ErrorResult rv;
      RefPtr<IdleDeadline> idleDeadline =
          new IdleDeadline(mParent, mTimedOut, deadline.ToMilliseconds());

      RefPtr<IdleRequestCallback> callback(std::move(mCallback));
      MOZ_ASSERT(!mCallback);
      callback->Call(*idleDeadline, "ChromeUtils::IdleDispatch handler");
      mParent = nullptr;
    }
    return NS_OK;
  }

  void SetDeadline(TimeStamp aDeadline) override { mDeadline = aDeadline; }

  NS_IMETHOD Notify(nsITimer* aTimer) override {
    mTimedOut = true;
    SetDeadline(TimeStamp::Now());
    return Run();
  }

  void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override {
    MOZ_ASSERT(aTarget);
    MOZ_ASSERT(!mTimer);
    NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aDelay,
                            nsITimer::TYPE_ONE_SHOT, aTarget);
  }

 protected:
  virtual ~IdleDispatchRunnable() { CancelTimer(); }

 private:
  void CancelTimer() {
    if (mTimer) {
      mTimer->Cancel();
      mTimer = nullptr;
    }
  }

  RefPtr<IdleRequestCallback> mCallback;
  nsCOMPtr<nsIGlobalObject> mParent;

  nsCOMPtr<nsITimer> mTimer;

  TimeStamp mDeadline{};
  bool mTimedOut = false;
};

NS_IMPL_ISUPPORTS_INHERITED(IdleDispatchRunnable, IdleRunnable,
                            nsITimerCallback)
}  // anonymous namespace

/* static */
void ChromeUtils::IdleDispatch(const GlobalObject& aGlobal,
                               IdleRequestCallback& aCallback,
                               const IdleRequestOptions& aOptions,
                               ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);

  auto runnable = MakeRefPtr<IdleDispatchRunnable>(global, aCallback);

  if (aOptions.mTimeout.WasPassed()) {
    aRv = NS_DispatchToCurrentThreadQueue(
        runnable.forget(), aOptions.mTimeout.Value(), EventQueuePriority::Idle);
  } else {
    aRv = NS_DispatchToCurrentThreadQueue(runnable.forget(),
                                          EventQueuePriority::Idle);
  }
}

static mozJSModuleLoader* GetModuleLoaderForCurrentGlobal(
    JSContext* aCx, const GlobalObject& aGlobal,
    Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
        aMaybeSyncLoaderScope) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());

  if (mozJSModuleLoader::IsSharedSystemGlobal(global)) {
    return mozJSModuleLoader::Get();
  }
  if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global)) {
    return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx);
  }

  if (loader::NonSharedGlobalSyncModuleLoaderScope::IsActive()) {
    mozJSModuleLoader* moduleloader =
        loader::NonSharedGlobalSyncModuleLoaderScope::ActiveLoader();

    if (!moduleloader->IsLoaderGlobal(global->GetGlobalJSObject())) {
      JS_ReportErrorASCII(aCx,
                          "global: \"current\" option cannot be used for "
                          "different global while other importESModule "
                          "with global: \"current\" is on the stack");
      return nullptr;
    }

    return moduleloader;
  }

  RefPtr targetModuleLoader = global->GetModuleLoader(aCx);
  if (!targetModuleLoader) {
    // Sandbox without associated window returns nullptr for GetModuleLoader.
    JS_ReportErrorASCII(aCx, "No ModuleLoader found for the current context");
    return nullptr;
  }

  if (targetModuleLoader->HasFetchingModules()) {
    if (!NS_IsMainThread()) {
      JS_ReportErrorASCII(aCx,
                          "ChromeUtils.importESModule cannot be used in worker "
                          "when there is ongoing dynamic import");
      return nullptr;
    }

    if (!mozilla::SpinEventLoopUntil(
            "importESModule for current global"_ns, [&]() -> bool {
              return !targetModuleLoader->HasFetchingModules();
            })) {
      JS_ReportErrorASCII(aCx, "Failed to wait for ongoing module requests");
      return nullptr;
    }
  }

  aMaybeSyncLoaderScope.emplace(aCx, global);
  return aMaybeSyncLoaderScope->ActiveLoader();
}

static mozJSModuleLoader* GetModuleLoaderForOptions(
    JSContext* aCx, const GlobalObject& aGlobal,
    const ImportESModuleOptionsDictionary& aOptions,
    Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
        aMaybeSyncLoaderScope) {
  if (!aOptions.mGlobal.WasPassed()) {
    return mozJSModuleLoader::Get();
  }

  switch (aOptions.mGlobal.Value()) {
    case ImportESModuleTargetGlobal::Shared:
      return mozJSModuleLoader::Get();

    case ImportESModuleTargetGlobal::Devtools:
      return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx);

    case ImportESModuleTargetGlobal::Contextual: {
      if (!NS_IsMainThread()) {
        return GetModuleLoaderForCurrentGlobal(aCx, aGlobal,
                                               aMaybeSyncLoaderScope);
      }

      RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader();
      if (devToolsModuleloader &&
          devToolsModuleloader->IsLoaderGlobal(aGlobal.Get())) {
        return mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx);
      }
      return mozJSModuleLoader::Get();
    }

    case ImportESModuleTargetGlobal::Current:
      return GetModuleLoaderForCurrentGlobal(aCx, aGlobal,
                                             aMaybeSyncLoaderScope);

    default:
      MOZ_CRASH("Unknown ImportESModuleTargetGlobal");
  }
}

static bool ValidateImportOptions(
    JSContext* aCx, const GlobalObject& aGlobal,
    const ImportESModuleOptionsDictionary& aOptions) {
  if (!NS_IsMainThread() &&
      (!aOptions.mGlobal.WasPassed() ||
       (aOptions.mGlobal.Value() != ImportESModuleTargetGlobal::Current &&
        aOptions.mGlobal.Value() != ImportESModuleTargetGlobal::Contextual))) {
    JS_ReportErrorASCII(aCx,
                        "ChromeUtils.importESModule: Only { global: "
                        "\"current\" } and { global: \"contextual\" } options "
                        "are supported on worker");
    return false;
  }

  if (NS_IsMainThread()) {
    nsCOMPtr<nsIGlobalObject> global =
        do_QueryInterface(aGlobal.GetAsSupports());

    if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global) &&
        !aOptions.mGlobal.WasPassed()) {
      JS_ReportErrorASCII(aCx,
                          "ChromeUtils.importESModule: global option is "
                          "required in DevTools distinct global");
      return false;
    }
  }

  return true;
}

/* static */
void ChromeUtils::ImportESModule(
    const GlobalObject& aGlobal, const nsAString& aResourceURI,
    const ImportESModuleOptionsDictionary& aOptions,
    JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();

  if (!ValidateImportOptions(cx, aGlobal, aOptions)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope;
  RefPtr<mozJSModuleLoader> moduleloader =
      GetModuleLoaderForOptions(cx, aGlobal, aOptions, maybeSyncLoaderScope);
  if (!moduleloader) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  NS_ConvertUTF16toUTF8 registryLocation(aResourceURI);

  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
      "ChromeUtils::ImportESModule", OTHER, registryLocation);

  JS::Rooted<JSObject*> moduleNamespace(cx);
  nsresult rv =
      moduleloader->ImportESModule(cx, registryLocation, &moduleNamespace);
  if (NS_FAILED(rv)) {
    if (maybeSyncLoaderScope) {
      // Import error itself should be propagated.
      maybeSyncLoaderScope->Finish();
    }
    aRv.Throw(rv);
    return;
  }

  MOZ_ASSERT(!JS_IsExceptionPending(cx));

  if (!JS_WrapObject(cx, &moduleNamespace)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }
  aRetval.set(moduleNamespace);

  if (maybeSyncLoaderScope) {
    maybeSyncLoaderScope->Finish();
  }
}

// An integer encoding for ImportESModuleOptionsDictionary, to pass the value
// to the lazy getters.
class EncodedOptions {
 public:
  explicit EncodedOptions(const ImportESModuleOptionsDictionary& aOptions) {
    if (aOptions.mGlobal.WasPassed()) {
      mValue = uint32_t(aOptions.mGlobal.Value()) + 1;
    } else {
      mValue = 0;
    }
  }

  explicit EncodedOptions(uint32_t aValue) : mValue(aValue) {}

  int32_t toInt32() const { return int32_t(mValue); }

  void DecodeInto(ImportESModuleOptionsDictionary& aOptions) {
    if (mValue == 0) {
      aOptions.mGlobal.Reset();
    } else {
      aOptions.mGlobal.Construct(ImportESModuleTargetGlobal(mValue - 1));
    }
  }

 private:
  uint32_t mValue = 0;
};

namespace lazy_getter {

// The property id of the getter.
// Used by all lazy getters.
static const size_t SLOT_ID = 0;

// The URI of the module to import.
// Used by ChromeUtils.defineESModuleGetters.
static const size_t SLOT_URI = 1;

// An array object that contians values for PARAM_INDEX_TARGET and
// PARAM_INDEX_LAMBDA.
// Used by ChromeUtils.defineLazyGetter.
static const size_t SLOT_PARAMS = 1;

// The EncodedOptions value.
// Used by ChromeUtils.defineESModuleGetters.
static const size_t SLOT_OPTIONS = 2;

static const size_t PARAM_INDEX_TARGET = 0;
static const size_t PARAM_INDEX_LAMBDA = 1;
static const size_t PARAMS_COUNT = 2;

static bool ExtractArgs(JSContext* aCx, JS::CallArgs& aArgs,
                        JS::MutableHandle<JSObject*> aCallee,
                        JS::MutableHandle<JSObject*> aThisObj,
                        JS::MutableHandle<jsid> aId) {
  aCallee.set(&aArgs.callee());

  JS::Handle<JS::Value> thisv = aArgs.thisv();
  if (!thisv.isObject()) {
    JS_ReportErrorASCII(aCx, "Invalid target object");
    return false;
  }

  aThisObj.set(&thisv.toObject());

  JS::Rooted<JS::Value> id(aCx,
                           js::GetFunctionNativeReserved(aCallee, SLOT_ID));
  MOZ_ALWAYS_TRUE(JS_ValueToId(aCx, id, aId));
  return true;
}

static bool JSLazyGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
  JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);

  JS::Rooted<JSObject*> callee(aCx);
  JS::Rooted<JSObject*> unused(aCx);
  JS::Rooted<jsid> id(aCx);
  if (!ExtractArgs(aCx, args, &callee, &unused, &id)) {
    return false;
  }

  JS::Rooted<JS::Value> paramsVal(
      aCx, js::GetFunctionNativeReserved(callee, SLOT_PARAMS));
  if (paramsVal.isUndefined()) {
    args.rval().setUndefined();
    return true;
  }
  // Avoid calling the lambda multiple times, in case of:
  //   * the getter function is retrieved from property descriptor and called
  //   * the lambda gets the property again
  //   * the getter function throws and accessed again
  js::SetFunctionNativeReserved(callee, SLOT_PARAMS, JS::UndefinedHandleValue);

  JS::Rooted<JSObject*> paramsObj(aCx, &paramsVal.toObject());

  JS::Rooted<JS::Value> targetVal(aCx);
  JS::Rooted<JS::Value> lambdaVal(aCx);
  if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_TARGET, &targetVal)) {
    return false;
  }
  if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_LAMBDA, &lambdaVal)) {
    return false;
  }

  JS::Rooted<JSObject*> targetObj(aCx, &targetVal.toObject());

  JS::Rooted<JS::Value> value(aCx);
  if (!JS::Call(aCx, targetObj, lambdaVal, JS::HandleValueArray::empty(),
                &value)) {
    return false;
  }

  if (!JS_DefinePropertyById(aCx, targetObj, id, value, JSPROP_ENUMERATE)) {
    return false;
  }

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

static bool DefineLazyGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
                             JS::Handle<JS::Value> aName,
                             JS::Handle<JSObject*> aLambda) {
  JS::Rooted<JS::PropertyKey> id(aCx);
  if (!JS_ValueToId(aCx, aName, &id)) {
    return false;
  }

  JS::Rooted<JS::PropertyKey> funId(aCx);
  if (id.isAtom()) {
    funId = id;
  } else {
    // Don't care int and symbol cases.
    funId = JS::PropertyKey::NonIntAtom(JS_GetEmptyString(aCx));
  }

  JS::Rooted<JSObject*> getter(
      aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
               aCx, JSLazyGetter, 0, 0, funId)));
  if (!getter) {
    JS_ReportOutOfMemory(aCx);
    return false;
  }

  JS::RootedVector<JS::Value> params(aCx);
  if (!params.resize(PARAMS_COUNT)) {
    return false;
  }
  params[PARAM_INDEX_TARGET].setObject(*aTarget);
  params[PARAM_INDEX_LAMBDA].setObject(*aLambda);
  JS::Rooted<JSObject*> paramsObj(aCx, JS::NewArrayObject(aCx, params));
  if (!paramsObj) {
    return false;
  }

  js::SetFunctionNativeReserved(getter, SLOT_ID, aName);
  js::SetFunctionNativeReserved(getter, SLOT_PARAMS,
                                JS::ObjectValue(*paramsObj));

  return JS_DefinePropertyById(aCx, aTarget, id, getter, nullptr,
                               JSPROP_ENUMERATE);
}

static bool ESModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
  JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);

  JS::Rooted<JSObject*> callee(aCx);
  JS::Rooted<JSObject*> thisObj(aCx);
  JS::Rooted<jsid> id(aCx);
  if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
    return false;
  }

  JS::Rooted<JSString*> moduleURI(
      aCx, js::GetFunctionNativeReserved(callee, SLOT_URI).toString());
  JS::UniqueChars bytes = JS_EncodeStringToUTF8(aCx, moduleURI);
  if (!bytes) {
    return false;
  }
  nsDependentCString uri(bytes.get());

  JS::Rooted<JS::Value> value(aCx);
  EncodedOptions encodedOptions(
      js::GetFunctionNativeReserved(callee, SLOT_OPTIONS).toInt32());

  ImportESModuleOptionsDictionary options;
  encodedOptions.DecodeInto(options);

  GlobalObject global(aCx, callee);

  Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope;
  RefPtr<mozJSModuleLoader> moduleloader =
      GetModuleLoaderForOptions(aCx, global, options, maybeSyncLoaderScope);
  if (!moduleloader) {
    return false;
  }

  JS::Rooted<JSObject*> moduleNamespace(aCx);
  nsresult rv = moduleloader->ImportESModule(aCx, uri, &moduleNamespace);
  if (NS_FAILED(rv)) {
    if (maybeSyncLoaderScope) {
      // Import error itself should be propagated.
      maybeSyncLoaderScope->Finish();
    }
    Throw(aCx, rv);
    return false;
  }

  // ESM's namespace is from the module's realm.
  {
    JSAutoRealm ar(aCx, moduleNamespace);
    if (!JS_GetPropertyById(aCx, moduleNamespace, id, &value)) {
      return false;
    }
  }
  if (!JS_WrapValue(aCx, &value)) {
    return false;
  }

  if (maybeSyncLoaderScope) {
    maybeSyncLoaderScope->Finish();
  }

  if (!JS_DefinePropertyById(aCx, thisObj, id, value, JSPROP_ENUMERATE)) {
    return false;
  }

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

static bool ESModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
  JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);

  JS::Rooted<JSObject*> callee(aCx);
  JS::Rooted<JSObject*> thisObj(aCx);
  JS::Rooted<jsid> id(aCx);
  if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
    return false;
  }

  return JS_DefinePropertyById(aCx, thisObj, id, args.get(0), JSPROP_ENUMERATE);
}

static bool DefineESModuleGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
                                 JS::Handle<JS::PropertyKey> aId,
                                 JS::Handle<JS::Value> aResourceURI,
                                 const EncodedOptions& encodedOptions) {
  JS::Rooted<JS::Value> idVal(aCx, JS::StringValue(aId.toString()));

  JS::Rooted<JS::Value> optionsVal(aCx,
                                   JS::Int32Value(encodedOptions.toInt32()));

  JS::Rooted<JSObject*> getter(
      aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
               aCx, ESModuleGetter, 0, 0, aId)));

  JS::Rooted<JSObject*> setter(
      aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
               aCx, ESModuleSetter, 0, 0, aId)));

  if (!getter || !setter) {
    JS_ReportOutOfMemory(aCx);
    return false;
  }

  js::SetFunctionNativeReserved(getter, SLOT_ID, idVal);
  js::SetFunctionNativeReserved(setter, SLOT_ID, idVal);

  js::SetFunctionNativeReserved(getter, SLOT_URI, aResourceURI);

  js::SetFunctionNativeReserved(getter, SLOT_OPTIONS, optionsVal);

  return JS_DefinePropertyById(aCx, aTarget, aId, getter, setter,
                               JSPROP_ENUMERATE);
}

}  // namespace lazy_getter

/* static */
void ChromeUtils::DefineLazyGetter(const GlobalObject& aGlobal,
                                   JS::Handle<JSObject*> aTarget,
                                   JS::Handle<JS::Value> aName,
                                   JS::Handle<JSObject*> aLambda,
                                   ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();
  if (!lazy_getter::DefineLazyGetter(cx, aTarget, aName, aLambda)) {
    aRv.NoteJSContextException(cx);
    return;
  }
}

/* static */
void ChromeUtils::DefineESModuleGetters(
    const GlobalObject& global, JS::Handle<JSObject*> target,
    JS::Handle<JSObject*> modules,
    const ImportESModuleOptionsDictionary& aOptions, ErrorResult& aRv) {
  JSContext* cx = global.Context();

  JS::Rooted<JS::IdVector> props(cx, JS::IdVector(cx));
  if (!JS_Enumerate(cx, modules, &props)) {
    aRv.NoteJSContextException(cx);
    return;
  }

  if (!ValidateImportOptions(cx, global, aOptions)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  EncodedOptions encodedOptions(aOptions);

  JS::Rooted<JS::PropertyKey> prop(cx);
  JS::Rooted<JS::Value> resourceURIVal(cx);
  for (JS::PropertyKey tmp : props) {
    prop = tmp;

    if (!prop.isString()) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }

    if (!JS_GetPropertyById(cx, modules, prop, &resourceURIVal)) {
      aRv.NoteJSContextException(cx);
      return;
    }

    if (!lazy_getter::DefineESModuleGetter(cx, target, prop, resourceURIVal,
                                           encodedOptions)) {
      aRv.NoteJSContextException(cx);
      return;
    }
  }
}

#ifdef XP_UNIX
/* static */
void ChromeUtils::GetLibcConstants(const GlobalObject&,
                                   LibcConstants& aConsts) {
  aConsts.mEPERM.Construct(EPERM);
  aConsts.mEINTR.Construct(EINTR);
  aConsts.mEACCES.Construct(EACCES);
  aConsts.mEAGAIN.Construct(EAGAIN);
  aConsts.mEINVAL.Construct(EINVAL);
  aConsts.mENOSYS.Construct(ENOSYS);

  aConsts.mF_SETFD.Construct(F_SETFD);
  aConsts.mF_SETFL.Construct(F_SETFL);

  aConsts.mFD_CLOEXEC.Construct(FD_CLOEXEC);

  aConsts.mAT_EACCESS.Construct(AT_EACCESS);

  aConsts.mO_CREAT.Construct(O_CREAT);
  aConsts.mO_NONBLOCK.Construct(O_NONBLOCK);
  aConsts.mO_WRONLY.Construct(O_WRONLY);
#  ifdef O_CLOEXEC
  aConsts.mO_CLOEXEC.Construct(O_CLOEXEC);
  // In the unlikely event of a target where O_CLOEXEC isn't defined
  // (Solaris 10?), the property will be absent in JS.
#  endif

  aConsts.mPOLLERR.Construct(POLLERR);
  aConsts.mPOLLHUP.Construct(POLLHUP);
  aConsts.mPOLLIN.Construct(POLLIN);
  aConsts.mPOLLNVAL.Construct(POLLNVAL);
  aConsts.mPOLLOUT.Construct(POLLOUT);

  aConsts.mWNOHANG.Construct(WNOHANG);

#  ifdef XP_LINUX
  aConsts.mPR_CAPBSET_READ.Construct(PR_CAPBSET_READ);
  aConsts.mO_PATH.Construct(O_PATH);
#  endif
}
#endif

/* static */
void ChromeUtils::OriginAttributesToSuffix(
    dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
    nsCString& aSuffix)

{
  OriginAttributes attrs(aAttrs);
  attrs.CreateSuffix(aSuffix);
}

/* static */
bool ChromeUtils::OriginAttributesMatchPattern(
    dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
    const dom::OriginAttributesPatternDictionary& aPattern) {
  OriginAttributes attrs(aAttrs);
  OriginAttributesPattern pattern(aPattern);
  return pattern.Matches(attrs);
}

/* static */
void ChromeUtils::CreateOriginAttributesFromOrigin(
    dom::GlobalObject& aGlobal, const nsAString& aOrigin,
    dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
  OriginAttributes attrs;
  nsAutoCString suffix;
  if (!attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(aOrigin), suffix)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }
  aAttrs = attrs;
}

/* static */
void ChromeUtils::CreateOriginAttributesFromOriginSuffix(
    dom::GlobalObject& aGlobal, const nsAString& aSuffix,
    dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
  OriginAttributes attrs;
  nsAutoCString suffix;
  if (!attrs.PopulateFromSuffix(NS_ConvertUTF16toUTF8(aSuffix))) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }
  aAttrs = attrs;
}

/* static */
void ChromeUtils::FillNonDefaultOriginAttributes(
    dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
    dom::OriginAttributesDictionary& aNewAttrs) {
  aNewAttrs = aAttrs;
}

/* static */
bool ChromeUtils::IsOriginAttributesEqual(
    dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aA,
    const dom::OriginAttributesDictionary& aB) {
  return IsOriginAttributesEqual(aA, aB);
}

/* static */
bool ChromeUtils::IsOriginAttributesEqual(
    const dom::OriginAttributesDictionary& aA,
    const dom::OriginAttributesDictionary& aB) {
  return aA == aB;
}

/* static */
void ChromeUtils::GetBaseDomainFromPartitionKey(dom::GlobalObject& aGlobal,
                                                const nsAString& aPartitionKey,
                                                nsAString& aBaseDomain,
                                                ErrorResult& aRv) {
  nsString scheme;
  nsString pkBaseDomain;
  int32_t port;
  bool ancestor;

  if (!mozilla::OriginAttributes::ParsePartitionKey(
          aPartitionKey, scheme, pkBaseDomain, port, ancestor)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  aBaseDomain = pkBaseDomain;
}

/* static */
void ChromeUtils::GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
                                         const nsAString& aTopLevelUrl,
                                         const nsAString& aSubresourceUrl,
                                         const Optional<bool>& aForeignContext,
                                         nsAString& aPartitionKey,
                                         ErrorResult& aRv) {
  nsCOMPtr<nsIURI> topLevelURI;
  nsresult rv = NS_NewURI(getter_AddRefs(topLevelURI), aTopLevelUrl);
  if (NS_SUCCEEDED(rv) && topLevelURI->SchemeIs("chrome")) {
    rv = NS_ERROR_FAILURE;
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aPartitionKey.Truncate();
    aRv.Throw(rv);
    return;
  }

  bool foreignResource;
  bool fallback = false;
  if (!aSubresourceUrl.IsEmpty()) {
    nsCOMPtr<nsIURI> resourceURI;
    rv = NS_NewURI(getter_AddRefs(resourceURI), aSubresourceUrl);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aPartitionKey.Truncate();
      aRv.Throw(rv);
      return;
    }

    ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
    if (!thirdPartyUtil) {
      aPartitionKey.Truncate();
      aRv.Throw(NS_ERROR_SERVICE_NOT_AVAILABLE);
      return;
    }

    rv = thirdPartyUtil->IsThirdPartyURI(topLevelURI, resourceURI,
                                         &foreignResource);
    if (NS_FAILED(rv)) {
      // we fallback to assuming the resource is foreign if there is an error
      foreignResource = true;
      fallback = true;
    }
  } else {
    // Assume we have a foreign resource if the resource was not provided
    foreignResource = true;
    fallback = true;
  }

  // aForeignContext is whether or not this is a foreign context.
  // foreignResource is whether or not the resource is cross-site to the top
  // level. So we need to validate that a false foreign context doesn't have a
  // same-site resource. That is impossible!
  if (aForeignContext.WasPassed() && !aForeignContext.Value() &&
      foreignResource && !fallback) {
    aPartitionKey.Truncate();
    aRv.Throw(nsresult::NS_ERROR_INVALID_ARG);
    return;
  }

  bool foreignByAncestorContext = aForeignContext.WasPassed() &&
                                  aForeignContext.Value() && !foreignResource;
  mozilla::OriginAttributes attrs;
  attrs.SetPartitionKey(topLevelURI, foreignByAncestorContext);
  aPartitionKey = attrs.mPartitionKey;
}

#ifdef NIGHTLY_BUILD
/* static */
void ChromeUtils::GetRecentJSDevError(GlobalObject& aGlobal,
                                      JS::MutableHandle<JS::Value> aRetval,
                                      ErrorResult& aRv) {
  aRetval.setUndefined();
  auto runtime = CycleCollectedJSRuntime::Get();
  MOZ_ASSERT(runtime);

  auto cx = aGlobal.Context();
  if (!runtime->GetRecentDevError(cx, aRetval)) {
    aRv.NoteJSContextException(cx);
    return;
  }
}

/* static */
void ChromeUtils::ClearRecentJSDevError(GlobalObject&) {
  auto runtime = CycleCollectedJSRuntime::Get();
  MOZ_ASSERT(runtime);

  runtime->ClearRecentDevError();
}
#endif  // NIGHTLY_BUILD

void ChromeUtils::ClearMessagingLayerSecurityStateByPrincipal(
    GlobalObject&, nsIPrincipal* aPrincipal, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, LogLevel::Debug,
          ("ClearMessagingLayerSecurityStateByPrincipal"));

  if (NS_WARN_IF(!aPrincipal)) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Principal is null"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Get the profile directory
  nsCOMPtr<nsIFile> file;
  aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(file));
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Append the 'mls' directory
  aRv = file->AppendNative("mls"_ns);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to append 'mls' to directory path"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  bool exists;
  aRv = file->Exists(&exists);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to check if 'mls' directory exists"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // If the 'mls' directory does not exist, we exit early
  if (!exists) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("'mls' directory does not exist"));
    return;
  }

  // Get the storage origin key
  nsAutoCString originKey;
  aRv = aPrincipal->GetStorageOriginKey(originKey);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get storage origin key"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Get the origin attributes suffix
  nsAutoCString originAttrSuffix;
  aRv = aPrincipal->GetOriginSuffix(originAttrSuffix);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to get origin attributes suffix"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Construct the full origin key
  nsAutoCString fullOriginKey = originKey + originAttrSuffix;

  // We append the full origin key to the file path
  aRv = file->AppendNative(fullOriginKey);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to append full origin key to the file path"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Remove the directory recursively
  aRv = file->Remove(/* recursive */ true);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to remove : %s", file->HumanReadablePath().get()));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  MOZ_LOG(gMlsLog, LogLevel::Debug,
          ("Successfully cleared MLS state for principal"));
}

void ChromeUtils::ClearMessagingLayerSecurityStateBySite(
    GlobalObject&, const nsACString& aSchemelessSite,
    const dom::OriginAttributesPatternDictionary& aPattern, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, LogLevel::Debug, ("ClearMessagingLayerSecurityStateBySite"));

  // Get the profile directory
  nsCOMPtr<nsIFile> file;
  aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(file));
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Append the 'mls' directory
  aRv = file->AppendNative("mls"_ns);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to append 'mls' to directory path"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  bool exists;
  aRv = file->Exists(&exists);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to check if 'mls' directory exists"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // If the 'mls' directory does not exist, we exit early
  if (!exists) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("'mls' directory does not exist"));
    return;
  }

  // Check if the schemeless site is empty
  if (NS_WARN_IF(aSchemelessSite.IsEmpty())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Schemeless site is empty"));
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  // Site pattern
  OriginAttributesPattern pattern(aPattern);

  // Partition pattern
  // This pattern is used to (additionally) clear state partitioned under
  // aSchemelessSite.
  OriginAttributesPattern partitionPattern = pattern;
  partitionPattern.mPartitionKeyPattern.Construct();
  partitionPattern.mPartitionKeyPattern.Value().mBaseDomain.Construct(
      NS_ConvertUTF8toUTF16(aSchemelessSite));

  // Reverse the base domain using the existing function
  nsAutoCString targetReversedBaseDomain(aSchemelessSite);
  std::reverse(targetReversedBaseDomain.BeginWriting(),
               targetReversedBaseDomain.EndWriting());

  MOZ_LOG(gMlsLog, LogLevel::Debug,
          ("Reversed base domain: %s", targetReversedBaseDomain.get()));

  // Enumerate files in the 'mls' directory
  nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
  aRv = file->GetDirectoryEntries(getter_AddRefs(dirEnum));
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to get directory entries in 'mls' directory"));
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // Iterate through all entries in the directory
  nsCOMPtr<nsIFile> entry;
  while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(entry))) && entry) {
    nsAutoCString entryName;
    aRv = entry->GetNativeLeafName(entryName);
    if (NS_WARN_IF(aRv.Failed())) {
      MOZ_LOG(gMlsLog, LogLevel::Error,
              ("Failed to get native leaf name for entry"));
      continue;
    }

    // Find the position of .sqlite.enc or .key in the entry name
    int32_t sqliteEncPos = entryName.RFind(".sqlite.enc");
    int32_t keyPos = entryName.RFind(".key");

    // Remove the .sqlite.enc or .key suffix from the entryName
    if (sqliteEncPos != kNotFound) {
      entryName.SetLength(sqliteEncPos);
    } else if (keyPos != kNotFound) {
      entryName.SetLength(keyPos);
    }

    // Decode the entry name
    nsAutoCString decodedEntryName;
    aRv = mozilla::Base64Decode(entryName, decodedEntryName);
    if (NS_WARN_IF(aRv.Failed())) {
      MOZ_LOG(gMlsLog, LogLevel::Debug,
              ("Failed to decode entry name: %s", entryName.get()));
      continue;
    }

    // Find the origin attributes suffix in the entry name by taking the
    // value of the entry name after the ^ separator
    int32_t separatorPos = decodedEntryName.FindChar('^');

    // We extract the origin attributes suffix from the entry name
    nsAutoCString originSuffix;
    originSuffix.Assign(Substring(decodedEntryName, separatorPos));

    // Populate the origin attributes from the suffix
    OriginAttributes originAttrs;
    if (NS_WARN_IF(!originAttrs.PopulateFromSuffix(originSuffix))) {
      MOZ_LOG(gMlsLog, LogLevel::Error,
              ("Failed to populate origin attributes from suffix"));
      continue;
    }

    // Check if the entry name starts with the reversed base domain
    if (StringBeginsWith(decodedEntryName, targetReversedBaseDomain)) {
      MOZ_LOG(gMlsLog, LogLevel::Debug,
              ("Entry file: %s", entry->HumanReadablePath().get()));

      // If there is a valid origin attributes suffix, we remove the entry
      // only if it matches.
      if (pattern.Matches(originAttrs)) {
        aRv = entry->Remove(/* recursive */ false);
        if (NS_WARN_IF(aRv.Failed())) {
          MOZ_LOG(gMlsLog, LogLevel::Error,
                  ("Failed to remove file: %s", decodedEntryName.get()));
        }
        MOZ_LOG(gMlsLog, LogLevel::Debug,
                ("Removed file: %s", decodedEntryName.get()));
      }
    }

    // If there is a valid origin attributes suffix, we remove the entry
    // only if it matches. We are checking for state partitioned under
    // aSchemelessSite.
    if (partitionPattern.Matches(originAttrs)) {
      aRv = entry->Remove(/* recursive */ false);
      if (NS_WARN_IF(aRv.Failed())) {
        MOZ_LOG(gMlsLog, LogLevel::Error,
                ("Failed to remove file: %s", decodedEntryName.get()));
      }
      MOZ_LOG(gMlsLog, LogLevel::Debug,
              ("Removed file: %s", decodedEntryName.get()));
    }
  }

  // Close the directory enumerator
  dirEnum->Close();
}

void ChromeUtils::ClearMessagingLayerSecurityState(GlobalObject&,
                                                   ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, LogLevel::Debug, ("ClearMessagingLayerSecurityState"));

  // Get the profile directory
  nsCOMPtr<nsIFile> file;
  aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(file));
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
    return;
  }

  // Append the 'mls' directory
  aRv = file->AppendNative("mls"_ns);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error,
            ("Failed to append 'mls' to directory path"));
    return;
  }

  // Check if the directory exists
  bool exists;
  aRv = file->Exists(&exists);
  if (NS_WARN_IF(aRv.Failed() || !exists)) {
    MOZ_LOG(gMlsLog, LogLevel::Debug, ("'mls' directory does not exist"));
    return;
  }

  // Remove the MLS directory recursively
  aRv = file->Remove(/* recursive */ true);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to remove MLS directory"));
    return;
  }

  // Log the directory path
  MOZ_LOG(gMlsLog, LogLevel::Debug,
          ("Deleted MLS directory: %s", file->HumanReadablePath().get()));

  // Recreate the MLS directory
  aRv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
  if (NS_WARN_IF(aRv.Failed())) {
    MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to recreate MLS directory"));
    return;
  }

  MOZ_LOG(gMlsLog, LogLevel::Debug, ("Successfully cleared all MLS state"));
}

void ChromeUtils::ClearResourceCache(
    GlobalObject& aGlobal, const dom::ClearResourceCacheOptions& aOptions,
    ErrorResult& aRv) {
  bool clearStyleSheet = false;
  bool clearScript = false;
  bool clearImage = false;

  if (aOptions.mTypes.WasPassed()) {
    for (const auto& type : aOptions.mTypes.Value()) {
      switch (type) {
        case ResourceCacheType::Stylesheet:
          clearStyleSheet = true;
          break;
        case ResourceCacheType::Script:
          clearScript = true;
          break;
        case ResourceCacheType::Image:
          clearImage = true;
          break;
      }
    }
  } else {
    clearStyleSheet = true;
    clearScript = true;
    clearImage = true;
  }

  int filterCount = 0;
  if (aOptions.mTarget.WasPassed()) {
    filterCount++;
  }
  if (aOptions.mPrincipal.WasPassed()) {
    filterCount++;
  }
  if (aOptions.mSchemelessSite.WasPassed()) {
    filterCount++;
  }
  if (aOptions.mUrl.WasPassed()) {
    filterCount++;
  }
  if (filterCount > 1) {
    aRv.ThrowInvalidStateError(
        "target, principal, schemelessSite, and url properties are mutually "
        "exclusive");
    return;
  }

  if (aOptions.mTarget.WasPassed()) {
    Maybe<bool> chrome;
    switch (aOptions.mTarget.Value()) {
      case ResourceCacheTarget::Chrome:
        chrome.emplace(true);
        break;
      case ResourceCacheTarget::Content:
        chrome.emplace(false);
        break;
    }

    if (clearStyleSheet) {
      SharedStyleSheetCache::Clear(chrome);
    }
    if (clearScript) {
      SharedScriptCache::Clear(chrome);
    }
    if (clearImage) {
      imgLoader::ClearCache(Nothing(), chrome);
    }
    return;
  }

  if (aOptions.mPrincipal.WasPassed()) {
    nsCOMPtr<nsIPrincipal> principal = aOptions.mPrincipal.Value().get();

    if (clearStyleSheet) {
      SharedStyleSheetCache::Clear(Nothing(), Some(principal));
    }
    if (clearScript) {
      SharedScriptCache::Clear(Nothing(), Some(principal));
    }
    if (clearImage) {
      imgLoader::ClearCache(Nothing(), Nothing(), Some(principal));
    }
    return;
  }

  if (aOptions.mSchemelessSite.WasPassed()) {
    nsCString schemelessSite(aOptions.mSchemelessSite.Value());
    mozilla::OriginAttributesPattern pattern(aOptions.mPattern);

    if (clearStyleSheet) {
      SharedStyleSheetCache::Clear(Nothing(), Nothing(), Some(schemelessSite),
                                   Some(pattern));
    }
    if (clearScript) {
      SharedScriptCache::Clear(Nothing(), Nothing(), Some(schemelessSite),
                               Some(pattern));
    }
    if (clearImage) {
      imgLoader::ClearCache(Nothing(), Nothing(), Nothing(),
                            Some(schemelessSite), Some(pattern));
    }
    return;
  }

  if (aOptions.mUrl.WasPassed()) {
    nsCString url(aOptions.mUrl.Value());

    if (clearStyleSheet) {
      SharedStyleSheetCache::Clear(Nothing(), Nothing(), Nothing(), Nothing(),
                                   Some(url));
    }
    if (clearScript) {
      SharedScriptCache::Clear(Nothing(), Nothing(), Nothing(), Nothing(),
                               Some(url));
    }
    if (clearImage) {
      imgLoader::ClearCache(Nothing(), Nothing(), Nothing(), Nothing(),
                            Nothing(), Some(url));
    }
    return;
  }

  if (clearStyleSheet) {
    SharedStyleSheetCache::Clear();
  }
  if (clearScript) {
    SharedScriptCache::Clear();
  }
  if (clearImage) {
    imgLoader::ClearCache();
  }
}

void ChromeUtils::InvalidateResourceCache(GlobalObject& aGlobal,
                                          ErrorResult& aRv) {
  SharedScriptCache::Invalidate();
}

void ChromeUtils::GetCachedJavaScriptSource(
    GlobalObject& aGlobal, const nsACString& aKey, const nsACString& aURI,
    const nsACString& aHintCharset, JS::MutableHandle<JS::Value> aRetval,
    ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();
  if (!SharedScriptCache::GetCachedScriptSource(cx, aKey, aURI, aHintCharset,
                                                aRetval)) {
    aRv.NoteJSContextException(aGlobal.Context());
  }
}

void ChromeUtils::ClearBfcacheByPrincipal(GlobalObject& aGlobal,
                                          nsIPrincipal* aPrincipal,
                                          ErrorResult& aRv) {
  aRv = CanonicalBrowsingContext::ClearBfcacheByPrincipal(aPrincipal);
}

#define PROCTYPE_TO_WEBIDL_CASE(_procType, _webidl) \
  case mozilla::ProcType::_procType:                \
    return WebIDLProcType::_webidl

static WebIDLProcType ProcTypeToWebIDL(mozilla::ProcType aType) {
  // Max is the value of the last enum, not the length, so add one.
  static_assert(
      static_cast<size_t>(MaxContiguousEnumValue<WebIDLProcType>::value) ==
          static_cast<size_t>(ProcType::Max),
      "In order for this static cast to be okay, "
      "WebIDLProcType must match ProcType exactly");

  // These must match the similar ones in E10SUtils.sys.mjs, RemoteTypes.h,
  // ProcInfo.h and ChromeUtils.webidl
  switch (aType) {
    PROCTYPE_TO_WEBIDL_CASE(Web, Web);
    PROCTYPE_TO_WEBIDL_CASE(WebIsolated, WebIsolated);
    PROCTYPE_TO_WEBIDL_CASE(File, File);
    PROCTYPE_TO_WEBIDL_CASE(Extension, Extension);
    PROCTYPE_TO_WEBIDL_CASE(PrivilegedAbout, Privilegedabout);
    PROCTYPE_TO_WEBIDL_CASE(PrivilegedMozilla, Privilegedmozilla);
    PROCTYPE_TO_WEBIDL_CASE(WebCOOPCOEP, WithCoopCoep);
    PROCTYPE_TO_WEBIDL_CASE(WebServiceWorker, WebServiceWorker);
    PROCTYPE_TO_WEBIDL_CASE(Inference, Inference);

#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
                           process_bin_type, procinfo_typename,               \
                           webidl_typename, allcaps_name)                     \
  PROCTYPE_TO_WEBIDL_CASE(procinfo_typename, webidl_typename);
#define SKIP_PROCESS_TYPE_CONTENT
#ifndef MOZ_ENABLE_FORKSERVER
#  define SKIP_PROCESS_TYPE_FORKSERVER
#endif  // MOZ_ENABLE_FORKSERVER
#include "mozilla/GeckoProcessTypes.h"
#undef SKIP_PROCESS_TYPE_CONTENT
#ifndef MOZ_ENABLE_FORKSERVER
#  undef SKIP_PROCESS_TYPE_FORKSERVER
#endif  // MOZ_ENABLE_FORKSERVER
#undef GECKO_PROCESS_TYPE

    PROCTYPE_TO_WEBIDL_CASE(Preallocated, Preallocated);
    PROCTYPE_TO_WEBIDL_CASE(Unknown, Unknown);
  }

  MOZ_ASSERT(false, "Unhandled case in ProcTypeToWebIDL");
  return WebIDLProcType::Unknown;
}

#undef PROCTYPE_TO_WEBIDL_CASE

/* static */
already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
                                                       ErrorResult& aRv) {
  // This function will use IPDL to enable threads info on macOS
  // see https://bugzilla.mozilla.org/show_bug.cgi?id=1529023
  if (!XRE_IsParentProcess()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }
  // Prepare the JS promise that will hold our response.
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);
  RefPtr<Promise> domPromise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  MOZ_ASSERT(domPromise);

  // Get a list of processes to examine and pre-fill them with available info.
  // Note that this is subject to race conditions: just because we have a
  // process in the list doesn't mean that the process will still be alive when
  // we attempt to get its information. Followup code MUST be able to fail
  // gracefully on some processes and still return whichever information is
  // available.

  // Get all the content parents.
  // Note that this array includes even the long dead content parents, so we
  // might have some garbage, especially with Fission.
  // SAFETY NOTE: `contentParents` is only valid if used synchronously.
  // Anything else and you may end up dealing with dangling pointers.
  nsTArray<ContentParent*> contentParents;
  ContentParent::GetAll(contentParents);

  // Prepare our background request.
  // We reserve one more slot for the browser process itself.
  nsTArray<ProcInfoRequest> requests(contentParents.Length() + 1);
  // Requesting process info for the browser process itself.
  requests.EmplaceBack(
      /* aPid = */ base::GetCurrentProcId(),
      /* aProcessType = */ ProcType::Browser,
      /* aOrigin = */ ""_ns,
      /* aWindowInfo = */ nsTArray<WindowInfo>(),
      /* aUtilityInfo = */ nsTArray<UtilityInfo>());

  // First handle non-ContentParent processes.
  mozilla::ipc::GeckoChildProcessHost::GetAll(
      [&requests](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
        base::ProcessId childPid = aGeckoProcess->GetChildProcessId();
        if (childPid == 0) {
          // Something went wrong with this process, it may be dead already,
          // fail gracefully.
          return;
        }
        mozilla::ProcType type = mozilla::ProcType::Unknown;

        switch (aGeckoProcess->GetProcessType()) {
          case GeckoProcessType::GeckoProcessType_Content: {
            // These processes are handled separately.
            return;
          }

#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
                           process_bin_type, procinfo_typename,               \
                           webidl_typename, allcaps_name)                     \
  case GeckoProcessType::GeckoProcessType_##enum_name: {                      \
    type = mozilla::ProcType::procinfo_typename;                              \
    break;                                                                    \
  }
#define SKIP_PROCESS_TYPE_CONTENT
#ifndef MOZ_ENABLE_FORKSERVER
#  define SKIP_PROCESS_TYPE_FORKSERVER
#endif  // MOZ_ENABLE_FORKSERVER
#include "mozilla/GeckoProcessTypes.h"
#ifndef MOZ_ENABLE_FORKSERVER
#  undef SKIP_PROCESS_TYPE_FORKSERVER
#endif  // MOZ_ENABLE_FORKSERVER
#undef SKIP_PROCESS_TYPE_CONTENT
#undef GECKO_PROCESS_TYPE
          default:
            // Leave the default Unknown value in |type|.
            break;
        }

        // Attach utility actor information to the process.
        nsTArray<UtilityInfo> utilityActors;
        if (aGeckoProcess->GetProcessType() ==
            GeckoProcessType::GeckoProcessType_Utility) {
          RefPtr<mozilla::ipc::UtilityProcessManager> upm =
              mozilla::ipc::UtilityProcessManager::GetSingleton();
          if (!utilityActors.AppendElements(upm->GetActors(aGeckoProcess),
                                            fallible)) {
            NS_WARNING("Error adding actors");
            return;
          }
        }

        requests.EmplaceBack(
            /* aPid = */ childPid,
            /* aProcessType = */ type,
            /* aOrigin = */ ""_ns,
            /* aWindowInfo = */ nsTArray<WindowInfo>(),  // Without a
                                                         // ContentProcess, no
                                                         // DOM windows.
            /* aUtilityInfo = */ std::move(utilityActors),
            /* aChild = */ 0  // Without a ContentProcess, no ChildId.
#ifdef XP_MACOSX
            ,
            /* aChildTask = */ aGeckoProcess->GetChildTask()
#endif  // XP_MACOSX
        );
      });

  // Now handle ContentParents.
  for (const auto* contentParent : contentParents) {
    if (!contentParent || !contentParent->Process()) {
      // Presumably, the process is dead or dying.
      continue;
    }
    base::ProcessId pid = contentParent->Process()->GetChildProcessId();
    if (pid == 0) {
      // Presumably, the process is dead or dying.
      continue;
    }
    if (contentParent->Process()->GetProcessType() !=
        GeckoProcessType::GeckoProcessType_Content) {
      // We're probably racing against a process changing type.
      // We'll get it in the next call, skip it for the moment.
      continue;
    }

    // Since this code is executed synchronously on the main thread,
    // processes cannot die while we're in this loop.
    mozilla::ProcType type = mozilla::ProcType::Unknown;

    // Convert the remoteType into a ProcType.
    // Ideally, the remoteType should be strongly typed
    // upstream, this would make the conversion less brittle.
    const nsAutoCString remoteType(contentParent->GetRemoteType());
    if (StringBeginsWith(remoteType, FISSION_WEB_REMOTE_TYPE)) {
      // WARNING: Do not change the order, as
      // `DEFAULT_REMOTE_TYPE` is a prefix of
      // `FISSION_WEB_REMOTE_TYPE`.
      type = mozilla::ProcType::WebIsolated;
    } else if (StringBeginsWith(remoteType, SERVICEWORKER_REMOTE_TYPE)) {
      type = mozilla::ProcType::WebServiceWorker;
    } else if (StringBeginsWith(remoteType,
                                WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
      type = mozilla::ProcType::WebCOOPCOEP;
    } else if (remoteType == FILE_REMOTE_TYPE) {
      type = mozilla::ProcType::File;
    } else if (remoteType == EXTENSION_REMOTE_TYPE) {
      type = mozilla::ProcType::Extension;
    } else if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
      type = mozilla::ProcType::PrivilegedAbout;
    } else if (remoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) {
      type = mozilla::ProcType::PrivilegedMozilla;
    } else if (remoteType == PREALLOC_REMOTE_TYPE) {
      type = mozilla::ProcType::Preallocated;
    } else if (remoteType == INFERENCE_REMOTE_TYPE) {
      type = mozilla::ProcType::Inference;
    } else if (StringBeginsWith(remoteType, DEFAULT_REMOTE_TYPE)) {
      type = mozilla::ProcType::Web;
    } else {
      MOZ_CRASH_UNSAFE_PRINTF("Unknown remoteType '%s'", remoteType.get());
    }

    // By convention, everything after '=' is the origin.
    nsAutoCString origin;
    nsACString::const_iterator cursor;
    nsACString::const_iterator end;
    remoteType.BeginReading(cursor);
    remoteType.EndReading(end);
    if (FindCharInReadable('=', cursor, end)) {
      origin = Substring(++cursor, end);
    }

    // Attach DOM window information to the process.
    nsTArray<WindowInfo> windows;
    for (const auto& browserParentWrapperKey :
         contentParent->ManagedPBrowserParent()) {
      for (const auto& windowGlobalParentWrapperKey :
           browserParentWrapperKey->ManagedPWindowGlobalParent()) {
        // WindowGlobalParent is the only immediate subclass of
        // PWindowGlobalParent.
        auto* windowGlobalParent =
            static_cast<WindowGlobalParent*>(windowGlobalParentWrapperKey);

        nsString documentTitle;
        windowGlobalParent->GetDocumentTitle(documentTitle);
        WindowInfo* window = windows.EmplaceBack(
            fallible,
            /* aOuterWindowId = */ windowGlobalParent->OuterWindowId(),
            /* aDocumentURI = */ windowGlobalParent->GetDocumentURI(),
            /* aDocumentTitle = */ std::move(documentTitle),
            /* aIsProcessRoot = */ windowGlobalParent->IsProcessRoot(),
            /* aIsInProcess = */ windowGlobalParent->IsInProcess());
        if (!window) {
          aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
          return nullptr;
        }
      }
    }
    requests.EmplaceBack(
        /* aPid = */ pid,
        /* aProcessType = */ type,
        /* aOrigin = */ origin,
        /* aWindowInfo = */ std::move(windows),
        /* aUtilityInfo = */ nsTArray<UtilityInfo>(),
        /* aChild = */ contentParent->ChildID()
#ifdef XP_MACOSX
            ,
        /* aChildTask = */ contentParent->Process()->GetChildTask()
#endif  // XP_MACOSX
    );
  }

  // Now place background request.
  RefPtr<nsISerialEventTarget> target = global->SerialEventTarget();
  mozilla::GetProcInfo(std::move(requests))
      ->Then(
          target, __func__,
          [target,
           domPromise](const HashMap<base::ProcessId, ProcInfo>& aSysProcInfo) {
            ParentProcInfoDictionary parentInfo;
            if (aSysProcInfo.count() == 0) {
              // For some reason, we couldn't get *any* info.
              // Maybe a sandboxing issue?
              domPromise->MaybeReject(NS_ERROR_UNEXPECTED);
              return;
            }
            nsTArray<ChildProcInfoDictionary> childrenInfo(
                aSysProcInfo.count() - 1);
            for (auto iter = aSysProcInfo.iter(); !iter.done(); iter.next()) {
              const auto& sysProcInfo = iter.get().value();
              nsresult rv;
              if (sysProcInfo.type == ProcType::Browser) {
                rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, &parentInfo);
                if (NS_FAILED(rv)) {
                  // Failing to copy? That's probably not something from we can
                  // (or should) try to recover gracefully.
                  domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
                  return;
                }
                MOZ_ASSERT(sysProcInfo.childId == 0);
                MOZ_ASSERT(sysProcInfo.origin.IsEmpty());
              } else {
                mozilla::dom::ChildProcInfoDictionary* childInfo =
                    childrenInfo.AppendElement(fallible);
                if (!childInfo) {
                  domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
                  return;
                }
                rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, childInfo);
                if (NS_FAILED(rv)) {
                  domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
                  return;
                }
                // Copy Firefox info.
                childInfo->mChildID = sysProcInfo.childId;
                childInfo->mOrigin = sysProcInfo.origin;
                childInfo->mType = ProcTypeToWebIDL(sysProcInfo.type);

                for (const auto& source : sysProcInfo.windows) {
                  auto* dest = childInfo->mWindows.AppendElement(fallible);
                  if (!dest) {
                    domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
                    return;
                  }
                  dest->mOuterWindowId = source.outerWindowId;
                  dest->mDocumentURI = source.documentURI;
                  dest->mDocumentTitle = source.documentTitle;
                  dest->mIsProcessRoot = source.isProcessRoot;
                  dest->mIsInProcess = source.isInProcess;
                }

                if (sysProcInfo.type == ProcType::Utility) {
                  for (const auto& source : sysProcInfo.utilityActors) {
                    auto* dest =
                        childInfo->mUtilityActors.AppendElement(fallible);
                    if (!dest) {
                      domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
                      return;
                    }

                    dest->mActorName = source.actorName;
                  }
                }
              }
            }

            // Attach the children to the parent.
            mozilla::dom::Sequence<mozilla::dom::ChildProcInfoDictionary>
                children(std::move(childrenInfo));
            parentInfo.mChildren = std::move(children);
            domPromise->MaybeResolve(parentInfo);
          },
          [domPromise](nsresult aRv) { domPromise->MaybeReject(aRv); });
  MOZ_ASSERT(domPromise);

  // sending back the promise instance
  return domPromise.forget();
}

/* static */
already_AddRefed<Promise> ChromeUtils::RequestXDGActivationToken(
    GlobalObject& aGlobal, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

#ifdef MOZ_WAYLAND
  if (RefPtr tokenPromise = widget::RequestWaylandFocusPromise()) {
    tokenPromise->Then(
        GetCurrentSerialEventTarget(), __func__,
        [promise](const nsCString& aToken) { promise->MaybeResolve(aToken); },
        [promise](bool) { promise->MaybeResolve(JS::NullHandleValue); });
    return promise.forget();
  }
#endif

  promise->MaybeResolve(JS::NullHandleValue);
  return promise.forget();
}

/* static */
uint64_t ChromeUtils::GetCurrentProcessMemoryUsage(GlobalObject& aGlobal,
                                                   ErrorResult& aRv) {
  uint64_t retVal = 0;
  nsresult rv = mozilla::GetCurrentProcessMemoryUsage(&retVal);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
  return retVal;
}

/* static */
uint64_t ChromeUtils::GetCpuTimeSinceProcessStart(GlobalObject& aGlobal,
                                                  ErrorResult& aRv) {
  uint64_t retVal = 0;
  nsresult rv = mozilla::GetCpuTimeSinceProcessStartInMs(&retVal);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
  return retVal;
}

/* static */
bool ChromeUtils::VsyncEnabled(GlobalObject& aGlobal) {
  return mozilla::gfx::VsyncSource::GetFastestVsyncRate().isSome();
}

void ChromeUtils::EnableAllPerfStatsFeatures(GlobalObject& aGlobal) {
  PerfStats::MetricMask mask =
      std::numeric_limits<PerfStats::MetricMask>::max();
  PerfStats::SetCollectionMask(mask);
}

void ChromeUtils::SetPerfStatsFeatures(GlobalObject& aGlobal,
                                       const Sequence<nsString>& aMetrics) {
  // Convert string array to bitmask
  PerfStats::MetricMask mask = 0;
  for (const auto& metricName : aMetrics) {
    // Convert string to corresponding enum value and set bit
    NS_ConvertUTF16toUTF8 utf8MetricName(metricName);
    mask |= PerfStats::GetFeatureMask(utf8MetricName.get());
  }

  PerfStats::SetCollectionMask(mask);
}

already_AddRefed<Promise> ChromeUtils::CollectPerfStats(GlobalObject& aGlobal,
                                                        ErrorResult& aRv) {
  // Creating a JS promise
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<PerfStats::PerfStatsPromise> extPromise =
      PerfStats::CollectPerfStatsJSON();

  extPromise->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise](const std::string& aResult) {
        promise->MaybeResolve(
            NS_ConvertUTF8toUTF16(aResult.c_str(), aResult.length()));
      },
      [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });

  return promise.forget();
}

constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;

/* static */
void ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal,
                                    nsIPrincipal* aPrincipal,
                                    JS::MutableHandle<JSObject*> aRetval) {
  JSContext* cx = aGlobal.Context();

  auto* principals = nsJSPrincipals::get(aPrincipal);

  JS::StackCapture captureMode(JS::FirstSubsumedFrame(cx, principals));

  JS::Rooted<JSObject*> frame(cx);
  if (!JS::CaptureCurrentStack(cx, &frame, std::move(captureMode))) {
    JS_ClearPendingException(cx);
    aRetval.set(nullptr);
    return;
  }

  // FirstSubsumedFrame gets us a stack which stops at the first principal which
  // is subsumed by the given principal. That means that we may have a lot of
  // privileged frames that we don't care about at the top of the stack, though.
  // We need to filter those out to get the frame we actually want.
  aRetval.set(
      js::GetFirstSubsumedSavedFrame(cx, principals, frame, kSkipSelfHosted));
}

/* static */
void ChromeUtils::CreateError(const GlobalObject& aGlobal,
                              const nsAString& aMessage,
                              JS::Handle<JSObject*> aStack,
                              JS::MutableHandle<JSObject*> aRetVal,
                              ErrorResult& aRv) {
  if (aStack && !JS::IsMaybeWrappedSavedFrame(aStack)) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  JSContext* cx = aGlobal.Context();

  auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });

  JS::Rooted<JSObject*> retVal(cx);
  {
    JS::Rooted<JSString*> fileName(cx, JS_GetEmptyString(cx));
    uint32_t line = 0;
    JS::TaggedColumnNumberOneOrigin column;

    Maybe<JSAutoRealm> ar;
    JS::Rooted<JSObject*> stack(cx);
    if (aStack) {
      stack = UncheckedUnwrap(aStack);
      ar.emplace(cx, stack);

      JSPrincipals* principals =
          JS::GetRealmPrincipals(js::GetContextRealm(cx));
      if (JS::GetSavedFrameLine(cx, principals, stack, &line) !=
              JS::SavedFrameResult::Ok ||
          JS::GetSavedFrameColumn(cx, principals, stack, &column) !=
              JS::SavedFrameResult::Ok ||
          JS::GetSavedFrameSource(cx, principals, stack, &fileName) !=
              JS::SavedFrameResult::Ok) {
        return;
      }
    }

    JS::Rooted<JSString*> message(cx);
    {
      JS::Rooted<JS::Value> msgVal(cx);
      if (!xpc::NonVoidStringToJsval(cx, aMessage, &msgVal)) {
        return;
      }
      message = msgVal.toString();
    }

    JS::Rooted<JS::Value> err(cx);
    if (!JS::CreateError(cx, JSEXN_ERR, stack, fileName, line,
                         JS::ColumnNumberOneOrigin(column.oneOriginValue()),
                         nullptr, message, JS::NothingHandleValue, &err)) {
      return;
    }

    MOZ_ASSERT(err.isObject());
    retVal = &err.toObject();
  }

  if (aStack && !JS_WrapObject(cx, &retVal)) {
    return;
  }

  cleanup.release();
  aRetVal.set(retVal);
}

/* static */
bool ChromeUtils::HasReportingHeaderForOrigin(GlobalObject& global,
                                              const nsAString& aOrigin,
                                              ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return false;
  }

  return ReportingHeader::HasReportingHeaderForOrigin(
      NS_ConvertUTF16toUTF8(aOrigin));
}

/* static */
PopupBlockerState ChromeUtils::GetPopupControlState(GlobalObject& aGlobal) {
  switch (PopupBlocker::GetPopupControlState()) {
    case PopupBlocker::PopupControlState::openAllowed:
      return PopupBlockerState::OpenAllowed;

    case PopupBlocker::PopupControlState::openControlled:
      return PopupBlockerState::OpenControlled;

    case PopupBlocker::PopupControlState::openBlocked:
      return PopupBlockerState::OpenBlocked;

    case PopupBlocker::PopupControlState::openAbused:
      return PopupBlockerState::OpenAbused;

    case PopupBlocker::PopupControlState::openOverridden:
      return PopupBlockerState::OpenOverridden;

    default:
      MOZ_CRASH(
          "PopupBlocker::PopupControlState and PopupBlockerState are out of "
          "sync");
  }
}

/* static */
double ChromeUtils::LastExternalProtocolIframeAllowed(GlobalObject& aGlobal) {
  TimeStamp when = PopupBlocker::WhenLastExternalProtocolIframeAllowed();
  if (when.IsNull()) {
    return 0;
  }

  TimeDuration duration = TimeStamp::Now() - when;
  return duration.ToMilliseconds();
}

/* static */
void ChromeUtils::ResetLastExternalProtocolIframeAllowed(
    GlobalObject& aGlobal) {
  PopupBlocker::ResetLastExternalProtocolIframeAllowed();
}

/* static */
already_AddRefed<Promise> ChromeUtils::EndWheelTransaction(
    GlobalObject& aGlobal, WindowProxyHolder& aWindow, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  // This allows us to end the current wheel transaction from the browser
  // chrome. We do not need to perform any checks before calling
  // EndTransaction(), as it should do nothing in the case that there is
  // no current wheel transaction.
  WheelTransaction::EndTransaction();

  // We also need to end the wheel transaction in APZ.
  nsIDocShell* docShell = aWindow.get()->GetDocShell();
  if (!docShell) {
    promise->MaybeResolveWithUndefined();
    return promise.forget();
  }

  nsIWidget* widget =
      nsContentUtils::GetWidget(docShell->GetPresShell(), nullptr);
  if (!widget) {
    promise->MaybeResolveWithUndefined();
    return promise.forget();
  }

  WindowRenderer* renderer = widget->GetWindowRenderer();
  if (!renderer) {
    promise->MaybeResolveWithUndefined();
    return promise.forget();
  }

  layers::WebRenderLayerManager* wr = renderer->AsWebRender();
  if (!wr) {
    promise->MaybeResolveWithUndefined();
    return promise.forget();
  }

  layers::WebRenderBridgeChild* wrbc = wr->WrBridge();
  if (!wrbc) {
    promise->MaybeResolveWithUndefined();
    return promise.forget();
  }

  wrbc->SendEndWheelTransaction()->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise](bool) { promise->MaybeResolveWithUndefined(); },
      [promise](mozilla::ipc::ResponseRejectReason) {
        promise->MaybeRejectWithUnknownError(
            "actor died while ending wheel transaction");
      });

  return promise.forget();
}

/* static */
void ChromeUtils::RegisterWindowActor(const GlobalObject& aGlobal,
                                      const nsACString& aName,
                                      const WindowActorOptions& aOptions,
                                      ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "registerWindowActor() may only be called in the parent process");
    return;
  }

  RefPtr<JSActorService> service = JSActorService::GetSingleton();
  service->RegisterWindowActor(aName, aOptions, aRv);
}

/* static */
void ChromeUtils::UnregisterWindowActor(const GlobalObject& aGlobal,
                                        const nsACString& aName,
                                        ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "unregisterWindowActor() may only be called in the parent process");
    return;
  }

  RefPtr<JSActorService> service = JSActorService::GetSingleton();
  service->UnregisterWindowActor(aName);
}

/* static */
void ChromeUtils::RegisterProcessActor(const GlobalObject& aGlobal,
                                       const nsACString& aName,
                                       const ProcessActorOptions& aOptions,
                                       ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "registerProcessActor() may only be called in the parent process");
    return;
  }

  RefPtr<JSActorService> service = JSActorService::GetSingleton();
  service->RegisterProcessActor(aName, aOptions, aRv);
}

/* static */
void ChromeUtils::UnregisterProcessActor(const GlobalObject& aGlobal,
                                         const nsACString& aName,
                                         ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "unregisterProcessActor() may only be called in the parent process");
    return;
  }

  RefPtr<JSActorService> service = JSActorService::GetSingleton();
  service->UnregisterProcessActor(aName);
}

/* static */
already_AddRefed<Promise> ChromeUtils::EnsureHeadlessContentProcess(
    const GlobalObject& aGlobal, const nsACString& aRemoteType,
    ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "ensureHeadlessContentProcess() may only be called in the parent "
        "process");
    return nullptr;
  }

  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  ContentParent::GetNewOrUsedBrowserProcessAsync(aRemoteType)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise](UniqueContentParentKeepAlive&& aKeepAlive) {
            nsCOMPtr<nsIContentParentKeepAlive> jsKeepAlive =
                WrapContentParentKeepAliveForJS(std::move(aKeepAlive));
            promise->MaybeResolve(jsKeepAlive);
          },
          [promise](nsresult aError) { promise->MaybeReject(aError); });
  return promise.forget();
}

/* static */
bool ChromeUtils::IsClassifierBlockingErrorCode(GlobalObject& aGlobal,
                                                uint32_t aError) {
  return net::ChannelClassifierUtils::IsClassifierBlockingErrorCode(
      static_cast<nsresult>(aError));
}

/* static */
void ChromeUtils::PrivateNoteIntentionalCrash(const GlobalObject& aGlobal,
                                              ErrorResult& aError) {
  if (XRE_IsContentProcess()) {
    NoteIntentionalCrash("tab");
    return;
  }
  aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
}

/* static */
nsIDOMProcessChild* ChromeUtils::GetDomProcessChild(const GlobalObject&) {
  return nsIDOMProcessChild::GetSingleton();
}

/* static */
void ChromeUtils::GetAllDOMProcesses(
    GlobalObject& aGlobal, nsTArray<RefPtr<nsIDOMProcessParent>>& aParents,
    ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "getAllDOMProcesses() may only be called in the parent process");
    return;
  }
  aParents.Clear();
  // Always add the parent process nsIDOMProcessParent first
  aParents.AppendElement(InProcessParent::Singleton());

  // Before adding nsIDOMProcessParent for all the content processes
  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    aParents.AppendElement(cp);
  }
}

/* static */
void ChromeUtils::ConsumeInteractionData(
    GlobalObject& aGlobal, Record<nsString, InteractionData>& aInteractions,
    ErrorResult& aRv) {
  if (!XRE_IsParentProcess()) {
    aRv.ThrowNotAllowedError(
        "consumeInteractionData() may only be called in the parent "
        "process");
    return;
  }
  EventStateManager::ConsumeInteractionData(aInteractions);
}

already_AddRefed<Promise> ChromeUtils::CollectScrollingData(
    GlobalObject& aGlobal, ErrorResult& aRv) {
  // Creating a JS promise
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<ScrollingMetrics::ScrollingMetricsPromise> extPromise =
      ScrollingMetrics::CollectScrollingMetrics();

  extPromise->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise](const std::tuple<uint32_t, uint32_t>& aResult) {
        InteractionData out = {};
        out.mInteractionTimeInMilliseconds = std::get<0>(aResult);
        out.mScrollingDistanceInPixels = std::get<1>(aResult);
        promise->MaybeResolve(out);
      },
      [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });

  return promise.forget();
}

/* static */
void ChromeUtils::GetFormAutofillConfidences(
    GlobalObject& aGlobal, const Sequence<OwningNonNull<Element>>& aElements,
    nsTArray<FormAutofillConfidences>& aResults, ErrorResult& aRv) {
  FormAutofillNative::GetFormAutofillConfidences(aGlobal, aElements, aResults,
                                                 aRv);
}

bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) {
  nsIFrame* f = aElement.GetPrimaryFrame(FlushType::Frames);
  if (!f) {
    return false;
  }
  return nsNativeTheme::IsDarkBackground(f);
}

double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; }

/* static */
double ChromeUtils::Now(GlobalObject&) {
  return (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
}

/* static */
void ChromeUtils::EnsureJSOracleStarted(GlobalObject&) {
  if (StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
    JSOracleParent::WithJSOracle([](JSOracleParent* aParent) {});
  }
}

/* static */
unsigned ChromeUtils::AliveUtilityProcesses(const GlobalObject&) {
  const auto& utilityProcessManager =
      mozilla::ipc::UtilityProcessManager::GetIfExists();
  return utilityProcessManager ? utilityProcessManager->AliveProcesses() : 0;
}

/* static */
void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
                                                  nsTArray<nsCString>& aNames) {
  aNames.Clear();
  for (UtilityActorName idlName :
       MakeWebIDLEnumeratedRange<WebIDLUtilityActorName>()) {
    aNames.AppendElement(GetEnumString(idlName));
  }
}

/* static */
bool ChromeUtils::ShouldResistFingerprinting(
    GlobalObject& aGlobal, JSRFPTarget aTarget,
    nsIRFPTargetSetIDL* aOverriddenFingerprintingSettings,
    const Optional<bool>& aIsPBM) {
  RFPTarget target;
#define JSRFP_TARGET_TO_RFP_TARGET(rfptarget) \
  case JSRFPTarget::rfptarget:                \
    target = RFPTarget::rfptarget;            \
    break;
  switch (aTarget) {
    JSRFP_TARGET_TO_RFP_TARGET(RoundWindowSize);
    JSRFP_TARGET_TO_RFP_TARGET(SiteSpecificZoom);
    JSRFP_TARGET_TO_RFP_TARGET(CSSPrefersColorScheme);
    JSRFP_TARGET_TO_RFP_TARGET(JSLocalePrompt);
    JSRFP_TARGET_TO_RFP_TARGET(HttpUserAgent);
    default:
      MOZ_CRASH("Unhandled JSRFPTarget enum value");
  }
#undef JSRFP_TARGET_TO_RFP_TARGET

  bool isPBM = false;
  if (aIsPBM.WasPassed()) {
    isPBM = aIsPBM.Value();
  } else {
    nsCOMPtr<nsIGlobalObject> global =
        do_QueryInterface(aGlobal.GetAsSupports());
    if (global) {
      nsPIDOMWindowInner* win = global->GetAsInnerWindow();
      if (win) {
        nsIDocShell* docshell = win->GetDocShell();
        if (docshell) {
          nsDocShell::Cast(docshell)->GetUsePrivateBrowsing(&isPBM);
        }
      }
    }
  }

  Maybe<RFPTargetSet> overriddenFingerprintingSettings;
  if (aOverriddenFingerprintingSettings) {
    overriddenFingerprintingSettings.emplace(
        static_cast<nsRFPTargetSetIDL*>(aOverriddenFingerprintingSettings)
            ->ToRFPTargetSet());
  }

  // This global object appears to be the global window, not for individual
  // sites so to exempt individual sites (instead of just PBM/Not-PBM windows)
  // more work would be needed to get the correct context.
  return nsRFPService::IsRFPEnabledFor(isPBM, target,
                                       overriddenFingerprintingSettings);
}

/* static */
void ChromeUtils::CallFunctionAndLogException(
    GlobalObject& aGlobal, JS::Handle<JS::Value> aTargetGlobal,
    JS::Handle<JS::Value> aFunction, JS::MutableHandle<JS::Value> aRetVal,
    ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();
  if (!aTargetGlobal.isObject() || !aFunction.isObject()) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx));
  if (!contextRealm) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  JS::Rooted<JSObject*> global(
      cx, js::CheckedUnwrapDynamic(&aTargetGlobal.toObject(), cx));
  if (!global) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException
  // which will do most of the work required for this function.
  //
  // We only have to pick the right global for which we want to flag
  // the exception against.
  dom::AutoJSAPI jsapi;
  if (!jsapi.Init(global)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }
  JSContext* ccx = jsapi.cx();

  // AutoJSAPI picks `aTargetGlobal` as execution compartment
  // whereas we expect to run `aFunction` from the callsites compartment.
  JSAutoRealm ar(ccx, JS::GetRealmGlobalOrNull(contextRealm));

  JS::Rooted<JS::Value> funVal(ccx, aFunction);
  if (!JS_WrapValue(ccx, &funVal)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }
  if (!JS_CallFunctionValue(ccx, nullptr, funVal, JS::HandleValueArray::empty(),
                            aRetVal)) {
    // Ensure re-throwing the exception which may have been thrown by
    // `aFunction`
    if (JS_IsExceptionPending(ccx)) {
      JS::Rooted<JS::Value> exception(cx);
      if (JS_GetPendingException(ccx, &exception)) {
        if (JS_WrapValue(cx, &exception)) {
          aRv.MightThrowJSException();
          aRv.ThrowJSException(cx, exception);
        }
      }
    }
  }
}

static Atomic<uint32_t, Relaxed> sDevToolsOpenedCount{0};

/* static */
bool ChromeUtils::IsDevToolsOpened() { return sDevToolsOpenedCount > 0; }

/* static */
bool ChromeUtils::IsDevToolsOpened(GlobalObject& aGlobal) {
  return IsDevToolsOpened();
}

/* static */
void ChromeUtils::NotifyDevToolsOpened(GlobalObject& aGlobal) {
  sDevToolsOpenedCount++;
}

/* static */
void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
  MOZ_ASSERT(sDevToolsOpenedCount >= 1);
  sDevToolsOpenedCount--;
}

/* static */
bool ChromeUtils::IsJSIdentifier(GlobalObject& aGlobal, const nsAString& aStr) {
  return JS_IsIdentifier(aStr.BeginReading(), aStr.Length());
}

#ifdef MOZ_WMF_CDM
/* static */
already_AddRefed<Promise> ChromeUtils::GetWMFContentDecryptionModuleInformation(
    GlobalObject& aGlobal, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);
  RefPtr<Promise> domPromise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  MOZ_ASSERT(domPromise);
  MFCDMService::GetAllKeySystemsCapabilities(domPromise);
  return domPromise.forget();
}
#endif

already_AddRefed<Promise> ChromeUtils::GetGMPContentDecryptionModuleInformation(
    GlobalObject& aGlobal, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);
  RefPtr<Promise> domPromise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  MOZ_ASSERT(domPromise);
  KeySystemConfig::GetGMPKeySystemConfigs(domPromise);
  return domPromise.forget();
}

void ChromeUtils::AndroidMoveTaskToBack(GlobalObject& aGlobal) {
#ifdef MOZ_WIDGET_ANDROID
  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
  java::GeckoAppShell::MoveTaskToBack();
#endif
}

already_AddRefed<nsIContentSecurityPolicy> ChromeUtils::CreateCSPFromHeader(
    GlobalObject& aGlobal, const nsAString& aHeader, nsIURI* aSelfURI,
    nsIPrincipal* aLoadingPrincipal, ErrorResult& aRv) {
  return CSP_CreateFromHeader(aHeader, aSelfURI, aLoadingPrincipal, aRv);
}

Nullable<bool> ChromeUtils::GetGlobalWindowCommandEnabled(
    GlobalObject&, const nsACString& aName) {
  const auto* table = nsControllerCommandTable::WindowCommandTable();
  RefPtr handler = table->FindCommandHandler(aName);
  if (!handler) {
    return nullptr;
  }
  return handler->IsCommandEnabled(aName, nullptr);
}

already_AddRefed<Promise> ChromeUtils::FetchDecodedImage(GlobalObject& aGlobal,
                                                         nsIURI* aURI,
                                                         nsIChannel* aChannel,
                                                         ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ASSERT(global);
  RefPtr<Promise> domPromise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  image::FetchDecodedImage(aURI, aChannel, gfx::IntSize{})
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [global, domPromise](already_AddRefed<imgIContainer> aImage) {
            nsCOMPtr<imgIContainer> image(std::move(aImage));

            AutoJSAPI jsapi;
            if (!jsapi.Init(global)) {
              domPromise->MaybeRejectWithUndefined();
              return;
            }

            JS::Rooted<JS::Value> value(jsapi.cx());
            if (!WrapObject(jsapi.cx(), image, &NS_GET_IID(imgIContainer),
                            &value)) {
              domPromise->MaybeRejectWithUndefined();
              return;
            }

            domPromise->MaybeResolve(value);
          },
          [domPromise](nsresult aStatus) { domPromise->MaybeReject(aStatus); });

  return domPromise.forget();
}

void ChromeUtils::EncodeURIForSrcset(GlobalObject&, const nsACString& aIn,
                                     nsACString& aOut) {
  const auto inputLen = aIn.Length();
  if (!inputLen) {
    return;
  }
  size_t start = 0;
  while (true) {
    auto idx = aIn.View().find_first_of(nsContentUtils::kHTMLWhitespace, start);
    if (idx == std::string_view::npos) {
      break;
    }
    aOut.Append(Substring(aIn, start, idx - start));
    aOut.AppendPrintf("%%%x", aIn.CharAt(idx));
    start = idx + 1;
    if (start == inputLen) {
      return;
    }
  }
  if (start == 0) {
    aOut.Assign(aIn);
  } else {
    aOut.Append(Substring(aIn, start));
  }
}

void ChromeUtils::GetLastOOMStackTrace(GlobalObject& aGlobal,
                                       nsAString& aRetval) {
  JSContext* cx = aGlobal.Context();
  aRetval = NS_ConvertUTF8toUTF16(JS_GetLastOOMStackTrace(cx));
}

void ChromeUtils::PredictRemoteTypeForURI(
    GlobalObject& aGlobal, nsIURI* aURI,
    const PredictRemoteTypeOptions& aOptions, nsACString& aRemoteType,
    ErrorResult& aRv) {
  // If 'useRemoteTabs' is disabled, immediately return with NOT_REMOTE_TYPE,
  // as we won't perform any process isolation.
  bool useRemoteTabs = true;
  if (aOptions.mUseRemoteTabs.WasPassed()) {
    useRemoteTabs = aOptions.mUseRemoteTabs.Value();
  } else if (aOptions.mWindow) {
    useRemoteTabs = aOptions.mWindow->GetBrowsingContext()->UseRemoteTabs();
  }
  if (!useRemoteTabs) {
    aRemoteType = NOT_REMOTE_TYPE;
    return;
  }

  bool useRemoteSubframes = true;
  if (aOptions.mUseRemoteSubframes.WasPassed()) {
    useRemoteSubframes = aOptions.mUseRemoteSubframes.Value();
  } else if (aOptions.mWindow) {
    useRemoteSubframes =
        aOptions.mWindow->GetBrowsingContext()->UseRemoteSubframes();
  }

  OriginAttributes attrs;
  attrs.mUserContextId = aOptions.mUserContextId;
  attrs.mGeckoViewSessionContextId = aOptions.mGeckoViewSessionContextId;
  if (aOptions.mPrivateBrowsingId.WasPassed()) {
    attrs.mPrivateBrowsingId = aOptions.mPrivateBrowsingId.Value();
  } else if (aOptions.mWindow) {
    attrs.mPrivateBrowsingId = aOptions.mWindow->IsPrivateBrowsing() ? 1 : 0;
  }

  nsCString preferredRemoteType = aOptions.mPreferredRemoteType.WasPassed()
                                      ? aOptions.mPreferredRemoteType.Value()
                                      : SharedWebRemoteType(attrs);

  // If we got nullptr as our argument URI argument, treat it like an
  // about:blank document, and load it into our preferred remote type.
  if (!aURI) {
    aRemoteType = preferredRemoteType;
    return;
  }

  auto result = mozilla::dom::PredictRemoteTypeForURI(
      aURI, attrs, preferredRemoteType, useRemoteSubframes);
  if (result.isErr()) {
    aRv.Throw(result.unwrapErr());
    return;
  }

  aRemoteType = result.unwrap();
}

void ChromeUtils::PredictRemoteTypeForURI(
    GlobalObject& aGlobal, const nsACString& aURIString,
    const PredictRemoteTypeOptions& aOptions, nsACString& aRemoteType,
    ErrorResult& aRv) {
  // Attempt to invoke URIFixup to fix up the given URI string into a functional
  // string. If this fails, `preferredURI` will be `nullptr`. We intentionally
  // don't forward any errors reported, as we want to recover in the case of an
  // invalid URI.
  nsCOMPtr<nsIURI> preferredURI;
  if (nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service()) {
    nsCOMPtr<nsIURIFixupInfo> fixupInfo;
    uriFixup->GetFixupURIInfo(aURIString, nsIURIFixup::FIXUP_FLAG_NONE,
                              getter_AddRefs(fixupInfo));
    if (fixupInfo) {
      fixupInfo->GetPreferredURI(getter_AddRefs(preferredURI));
    }
  }

  // If parsing the URI with fixup failed, clear out `mPreferredRemoteType`, so
  // we always fall back to the least privileged remote type.
  PredictRemoteTypeOptions newOptions(aOptions);
  if (!preferredURI) {
    newOptions.mPreferredRemoteType.Reset();
  }

  PredictRemoteTypeForURI(aGlobal, preferredURI, newOptions, aRemoteType, aRv);
}

}  // namespace mozilla::dom
