bool extract_body(JSContext *cx, HandleObject self, HandleValue body_val) {
  MOZ_ASSERT(is_instance(self));
  MOZ_ASSERT(!has_body(self));
  MOZ_ASSERT(!body_val.isNullOrUndefined());

  const char *content_type = nullptr;

  // We currently support five types of body inputs:
  // - byte sequence
  // - buffer source
  // - USV strings
  // - URLSearchParams
  // - ReadableStream
  // After the other other options are checked explicitly, all other inputs are
  // encoded to a UTF8 string to be treated as a USV string.
  // TODO: Support the other possible inputs to Body.

  RootedObject body_obj(cx, body_val.isObject() ? &body_val.toObject() : nullptr);

  if (body_obj && JS::IsReadableStream(body_obj)) {
    if (body_unusable(cx, body_obj)) {
      JS_ReportErrorLatin1(cx, "Can't use a ReadableStream that's locked or has ever been "
                               "read from or canceled as a Request or Response body.");
      return false;
    }

    JS_SetReservedSlot(self, RequestOrResponse::Slots::BodyStream, body_val);

    // Ensure that we take the right steps for shortcutting operations on
    // TransformStreams later on.
    if (builtins::TransformStream::is_ts_readable(cx, body_obj)) {
      // But only if the TransformStream isn't used as a mixin by other
      // builtins.
      if (!builtins::TransformStream::used_as_mixin(
              builtins::TransformStream::ts_from_readable(cx, body_obj))) {
        builtins::TransformStream::set_readable_used_as_body(cx, body_obj, self);
      }
    }
  } else {
    mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC;
    UniqueChars text;
    char *buf;
    size_t length;

    if (body_obj && JS_IsArrayBufferViewObject(body_obj)) {
      // Short typed arrays have inline data which can move on GC, so assert
      // that no GC happens. (Which it doesn't, because we're not allocating
      // before `buf` goes out of scope.)
      maybeNoGC.emplace(cx);
      JS::AutoCheckCannotGC &noGC = maybeNoGC.ref();
      bool is_shared;
      length = JS_GetArrayBufferViewByteLength(body_obj);
      buf = (char *)JS_GetArrayBufferViewData(body_obj, &is_shared, noGC);
    } else if (body_obj && JS::IsArrayBufferObject(body_obj)) {
      bool is_shared;
      JS::GetArrayBufferLengthAndData(body_obj, &length, &is_shared, (uint8_t **)&buf);
    } else if (body_obj && URLSearchParams::is_instance(body_obj)) {
      SpecSlice slice = URLSearchParams::serialize(cx, body_obj);
      buf = (char *)slice.data;
      length = slice.len;
      content_type = "application/x-www-form-urlencoded;charset=UTF-8";
    } else {
      text = encode(cx, body_val, &length);
      if (!text)
        return false;
      buf = text.get();
      content_type = "text/plain;charset=UTF-8";
    }

    BodyHandle body_handle = RequestOrResponse::body_handle(self);
    int result = write_to_body_all(body_handle, buf, length);

    // Ensure that the NoGC is reset, so throwing an error in HANDLE_RESULT
    // succeeds.
    if (maybeNoGC.isSome()) {
      maybeNoGC.reset();
    }

    if (!HANDLE_RESULT(cx, result))
      return false;
  }

  // Step 36.3 of Request constructor / 8.4 of Response constructor.
  if (content_type) {
    RootedObject headers(cx, &JS::GetReservedSlot(self, Slots::Headers).toObject());
    if (!Headers::maybe_add(cx, headers, "content-type", content_type)) {
      return false;
    }
  }

  JS::SetReservedSlot(self, Slots::HasBody, JS::BooleanValue(true));
  return true;
}