// Copyright 2018-2026 the Deno authors. MIT license.

// @ts-check
/// <reference no-default-lib="true" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../../core/internal.d.ts" />
/// <reference path="../webidl/internal.d.ts" />
/// <reference path="../web/internal.d.ts" />
/// <reference path="../../cli/tsc/dts/lib.deno_web.d.ts" />
/// <reference path="./internal.d.ts" />
/// <reference lib="esnext" />

(function () {
const { core, primordials } = __bootstrap;
const {
  isAnyArrayBuffer,
  isArrayBuffer,
  isDataView,
  isTypedArray,
} = core;
const {
  op_blob_clone_part,
  op_blob_create_object_url,
  op_blob_create_part,
  op_blob_from_object_url,
  op_blob_read_part,
  op_blob_remove_part,
  op_blob_revoke_object_url,
  op_blob_slice_part,
} = core.ops;
const {
  ArrayBufferIsView,
  ArrayBufferPrototypeGetByteLength,
  ArrayBufferPrototypeSlice,
  ArrayPrototypePush,
  AsyncGeneratorPrototypeNext,
  DataViewPrototypeGetBuffer,
  DataViewPrototypeGetByteLength,
  DataViewPrototypeGetByteOffset,
  Date,
  DatePrototypeGetTime,
  MathMax,
  MathMin,
  ObjectDefineProperty,
  ObjectPrototypeIsPrototypeOf,
  RegExpPrototypeTest,
  SafeArrayIterator,
  SafeFinalizationRegistry,
  SafeRegExp,
  StringPrototypeCharAt,
  StringPrototypeSlice,
  StringPrototypeToLowerCase,
  Symbol,
  SymbolFor,
  TypeError,
  TypedArrayPrototypeGetBuffer,
  TypedArrayPrototypeGetByteLength,
  TypedArrayPrototypeGetByteOffset,
  TypedArrayPrototypeSet,
  Uint8Array,
} = primordials;

const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
// Defer loading the 208 KB `06_streams.js` polyfill: ReadableStream is
// only constructed inside `Blob.stream()` (see usage below), so we don't
// need to pay the parse cost at module body time.
let _readableStream;
function ReadableStream(...args) {
  return new (_readableStream ??
    (_readableStream =
      core.loadExtScript("ext:deno_web/06_streams.js").ReadableStream))(
    ...new SafeArrayIterator(args),
  );
}
// Defer loading `08_text_encoding.js` for the same reason: `TextDecoderStream`
// pulls in the 06_streams.js polyfill, and is only constructed inside
// `Blob.textStream()` (see usage below).
let _textDecoderStream;
function TextDecoderStream(...args) {
  return new (_textDecoderStream ??
    (_textDecoderStream = core.loadExtScript("ext:deno_web/08_text_encoding.js")
      .TextDecoderStream))(
    ...new SafeArrayIterator(args),
  );
}
const { URL } = core.loadExtScript("ext:deno_web/00_url.js");
const { createFilteredInspectProxy } = core.loadExtScript(
  "ext:deno_web/01_console.js",
);

// TODO(lucacasonato): this needs to not be hardcoded and instead depend on
// host os.
const isWindows = false;

/**
 * @param {string} input
 * @param {number} position
 * @returns {{result: string, position: number}}
 */
function collectCodepointsNotCRLF(input, position) {
  // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and
  // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
  const start = position;
  for (
    let c = StringPrototypeCharAt(input, position);
    position < input.length && !(c === "\r" || c === "\n");
    c = StringPrototypeCharAt(input, ++position)
  );
  return { result: StringPrototypeSlice(input, start, position), position };
}

/**
 * @param {string} s
 * @returns {string}
 */
function convertLineEndingsToNative(s) {
  const nativeLineEnding = isWindows ? "\r\n" : "\n";

  let { result, position } = collectCodepointsNotCRLF(s, 0);

  while (position < s.length) {
    const codePoint = StringPrototypeCharAt(s, position);
    if (codePoint === "\r") {
      result += nativeLineEnding;
      position++;
      if (
        position < s.length && StringPrototypeCharAt(s, position) === "\n"
      ) {
        position++;
      }
    } else if (codePoint === "\n") {
      position++;
      result += nativeLineEnding;
    }
    const { result: token, position: newPosition } = collectCodepointsNotCRLF(
      s,
      position,
    );
    position = newPosition;
    result += token;
  }

  return result;
}

/** @param {(BlobReference | Blob)[]} parts */
async function* toIterator(parts) {
  for (let i = 0; i < parts.length; ++i) {
    // deno-lint-ignore deno-internal/prefer-primordials
    yield* parts[i].stream();
  }
}

/** @typedef {BufferSource | Blob | string} BlobPart */

/**
 * @param {BlobPart[]} parts
 * @param {string} endings
 * @returns {{ parts: (BlobReference|Blob)[], size: number }}
 */
function processBlobParts(parts, endings) {
  /** @type {(BlobReference|Blob)[]} */
  const processedParts = [];
  let size = 0;
  for (let i = 0; i < parts.length; ++i) {
    const element = parts[i];
    if (isArrayBuffer(element)) {
      const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0));
      ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk));
      size += ArrayBufferPrototypeGetByteLength(element);
    } else if (isTypedArray(element)) {
      const chunk = new Uint8Array(
        TypedArrayPrototypeGetBuffer(/** @type {Uint8Array} */ (element)),
        TypedArrayPrototypeGetByteOffset(/** @type {Uint8Array} */ (element)),
        TypedArrayPrototypeGetByteLength(/** @type {Uint8Array} */ (element)),
      );
      size += TypedArrayPrototypeGetByteLength(
        /** @type {Uint8Array} */ (element),
      );
      ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk));
    } else if (isDataView(element)) {
      const chunk = new Uint8Array(
        DataViewPrototypeGetBuffer(/** @type {DataView} */ (element)),
        DataViewPrototypeGetByteOffset(/** @type {DataView} */ (element)),
        DataViewPrototypeGetByteLength(/** @type {DataView} */ (element)),
      );
      size += DataViewPrototypeGetByteLength(
        /** @type {DataView} */ (element),
      );
      ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk));
    } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, element)) {
      ArrayPrototypePush(processedParts, element);
      size += element.size;
    } else if (typeof element === "string") {
      const chunk = core.encode(
        endings == "native" ? convertLineEndingsToNative(element) : element,
      );
      size += TypedArrayPrototypeGetByteLength(chunk);
      ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk));
    } else {
      throw new TypeError("Unreachable code (invalid element type)");
    }
  }
  return { parts: processedParts, size };
}

const NORMALIZE_PATTERN = new SafeRegExp(/^[\x20-\x7E]*$/);

/**
 * @param {string} str
 * @returns {string}
 */
function normalizeType(str) {
  let normalizedType = str;
  if (!RegExpPrototypeTest(NORMALIZE_PATTERN, str)) {
    normalizedType = "";
  }
  return StringPrototypeToLowerCase(normalizedType);
}

/**
 * Get all Parts as a flat array containing all references
 * @param {Blob} blob
 * @param {string[]} bag
 * @returns {string[]}
 */
function getParts(blob, bag = []) {
  const parts = blob[_parts];
  for (let i = 0; i < parts.length; ++i) {
    const part = parts[i];
    if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) {
      getParts(part, bag);
    } else {
      ArrayPrototypePush(bag, part._id);
    }
  }
  return bag;
}

const _type = Symbol("Type");
const _size = Symbol("Size");
const _parts = Symbol("Parts");
const _fileBacked = Symbol("FileBacked");

/** @param {(BlobReference | Blob)[]} parts */
function hasFileBackedPart(parts) {
  for (let i = 0; i < parts.length; ++i) {
    const part = parts[i];
    if (
      ObjectPrototypeIsPrototypeOf(BlobPrototype, part) && part[_fileBacked]
    ) {
      return true;
    }
  }
  return false;
}

class Blob {
  [_type] = "";
  [_size] = 0;
  [_parts];
  [_fileBacked] = false;

  /**
   * @param {BlobPart[]} blobParts
   * @param {BlobPropertyBag} options
   */
  constructor(blobParts = [], options = { __proto__: null }) {
    const prefix = "Failed to construct 'Blob'";
    blobParts = webidl.converters["sequence<BlobPart>"](
      blobParts,
      prefix,
      "Argument 1",
    );
    options = webidl.converters["BlobPropertyBag"](
      options,
      prefix,
      "Argument 2",
    );

    this[webidl.brand] = webidl.brand;

    const { parts, size } = processBlobParts(
      blobParts,
      options.endings,
    );

    this[_parts] = parts;
    this[_size] = size;
    this[_type] = normalizeType(options.type);
    this[_fileBacked] = hasFileBackedPart(parts);
  }

  /** @returns {number} */
  get size() {
    webidl.assertBranded(this, BlobPrototype);
    return this[_size];
  }

  /** @returns {string} */
  get type() {
    webidl.assertBranded(this, BlobPrototype);
    return this[_type];
  }

  /**
   * @param {number} [start]
   * @param {number} [end]
   * @param {string} [contentType]
   * @returns {Blob}
   */
  slice(start = undefined, end = undefined, contentType = undefined) {
    webidl.assertBranded(this, BlobPrototype);
    const prefix = "Failed to execute 'slice' on 'Blob'";
    if (start !== undefined) {
      start = webidl.converters["long long"](start, prefix, "Argument 1", {
        clamp: true,
      });
    }
    if (end !== undefined) {
      end = webidl.converters["long long"](end, prefix, "Argument 2", {
        clamp: true,
      });
    }
    if (contentType !== undefined) {
      contentType = webidl.converters["DOMString"](
        contentType,
        prefix,
        "Argument 3",
      );
    }

    // deno-lint-ignore no-this-alias
    const O = this;
    /** @type {number} */
    let relativeStart;
    if (start === undefined) {
      relativeStart = 0;
    } else {
      if (start < 0) {
        relativeStart = MathMax(O.size + start, 0);
      } else {
        relativeStart = MathMin(start, O.size);
      }
    }
    /** @type {number} */
    let relativeEnd;
    if (end === undefined) {
      relativeEnd = O.size;
    } else {
      if (end < 0) {
        relativeEnd = MathMax(O.size + end, 0);
      } else {
        relativeEnd = MathMin(end, O.size);
      }
    }

    const span = MathMax(relativeEnd - relativeStart, 0);
    const blobParts = [];
    let added = 0;

    const parts = this[_parts];
    for (let i = 0; i < parts.length; ++i) {
      const part = parts[i];
      // don't add the overflow to new blobParts
      if (added >= span) {
        // Could maybe be possible to remove variable `added`
        // and only use relativeEnd?
        break;
      }
      const size = part.size;
      if (relativeStart && size <= relativeStart) {
        // Skip the beginning and change the relative
        // start & end position as we skip the unwanted parts
        relativeStart -= size;
        relativeEnd -= size;
      } else {
        // deno-lint-ignore deno-internal/prefer-primordials
        const chunk = part.slice(
          relativeStart,
          MathMin(part.size, relativeEnd),
        );
        added += chunk.size;
        relativeEnd -= part.size;
        ArrayPrototypePush(blobParts, chunk);
        relativeStart = 0; // All next sequential parts should start at 0
      }
    }

    /** @type {string} */
    let relativeContentType;
    if (contentType === undefined) {
      relativeContentType = "";
    } else {
      relativeContentType = normalizeType(contentType);
    }

    const blob = new Blob([], { type: relativeContentType });
    blob[_parts] = blobParts;
    blob[_size] = span;
    blob[_fileBacked] = this[_fileBacked];
    return blob;
  }

  /**
   * @returns {ReadableStream<Uint8Array>}
   */
  stream() {
    webidl.assertBranded(this, BlobPrototype);
    const partIterator = toIterator(this[_parts]);
    const stream = new ReadableStream({
      type: "bytes",
      /** @param {ReadableByteStreamController} controller */
      async pull(controller) {
        while (true) {
          const { value, done } = await AsyncGeneratorPrototypeNext(
            partIterator,
          );
          if (done) {
            controller.close();
            controller.byobRequest?.respond(0);
            return;
          }
          if (TypedArrayPrototypeGetByteLength(value) > 0) {
            return controller.enqueue(value);
          }
        }
      },
    });
    return stream;
  }

  /**
   * @returns {ReadableStream<string>}
   */
  textStream() {
    webidl.assertBranded(this, BlobPrototype);
    return this.stream().pipeThrough(new TextDecoderStream());
  }

  /**
   * @param {number} size
   * @returns {Promise<Uint8Array>}
   */
  async #u8Array(size) {
    const bytes = new Uint8Array(size);
    const partIterator = toIterator(this[_parts]);
    let offset = 0;
    while (true) {
      const { value, done } = await AsyncGeneratorPrototypeNext(
        partIterator,
      );
      if (done) break;
      const byteLength = TypedArrayPrototypeGetByteLength(value);
      if (byteLength > 0) {
        TypedArrayPrototypeSet(bytes, value, offset);
        offset += byteLength;
      }
    }
    return bytes;
  }

  /**
   * @returns {Promise<string>}
   */
  async text() {
    webidl.assertBranded(this, BlobPrototype);
    const buffer = await this.#u8Array(this.size);
    return core.decode(buffer);
  }

  /**
   * @returns {Promise<ArrayBuffer>}
   */
  async arrayBuffer() {
    webidl.assertBranded(this, BlobPrototype);
    const buf = await this.#u8Array(this.size);
    return TypedArrayPrototypeGetBuffer(buf);
  }

  /**
   * @returns {Promise<Uint8Array>}
   */
  async bytes() {
    webidl.assertBranded(this, BlobPrototype);
    return await this.#u8Array(this.size);
  }

  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    return inspect(
      createFilteredInspectProxy({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(BlobPrototype, this),
        keys: [
          "size",
          "type",
        ],
      }),
      inspectOptions,
    );
  }
}

webidl.configureInterface(Blob);
const BlobPrototype = Blob.prototype;

webidl.converters["Blob"] = webidl.createInterfaceConverter(
  "Blob",
  Blob.prototype,
);
webidl.converters["BlobPart"] = (V, prefix, context, opts) => {
  // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString)
  if (typeof V == "object") {
    if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) {
      return webidl.converters["Blob"](V, prefix, context, opts);
    }
    if (isAnyArrayBuffer(V)) {
      return webidl.converters["ArrayBuffer"](V, prefix, context, opts);
    }
    if (ArrayBufferIsView(V)) {
      return webidl.converters["ArrayBufferView"](V, prefix, context, opts);
    }
  }
  // BlobPart is passed to processBlobParts after conversion, which calls core.encode()
  // on the string.
  // core.encode() is equivalent to USVString normalization.
  return webidl.converters["DOMString"](V, prefix, context, opts);
};
webidl.converters["sequence<BlobPart>"] = webidl.createSequenceConverter(
  webidl.converters["BlobPart"],
);
webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [
  "transparent",
  "native",
]);
const blobPropertyBagDictionary = [
  {
    key: "type",
    converter: webidl.converters["DOMString"],
    defaultValue: "",
  },
  {
    key: "endings",
    converter: webidl.converters["EndingType"],
    defaultValue: "transparent",
  },
];
webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter(
  "BlobPropertyBag",
  blobPropertyBagDictionary,
);

const _Name = Symbol("[[Name]]");
const _LastModified = Symbol("[[LastModified]]");

class File extends Blob {
  /** @type {string} */
  [_Name];
  /** @type {number} */
  [_LastModified];

  /**
   * @param {BlobPart[]} fileBits
   * @param {string} fileName
   * @param {FilePropertyBag} options
   */
  constructor(fileBits, fileName, options = { __proto__: null }) {
    const prefix = "Failed to construct 'File'";
    webidl.requiredArguments(arguments.length, 2, prefix);

    fileBits = webidl.converters["sequence<BlobPart>"](
      fileBits,
      prefix,
      "Argument 1",
    );
    fileName = webidl.converters["USVString"](fileName, prefix, "Argument 2");
    options = webidl.converters["FilePropertyBag"](
      options,
      prefix,
      "Argument 3",
    );

    super(fileBits, options);

    /** @type {string} */
    this[_Name] = fileName;
    if (options.lastModified === undefined) {
      /** @type {number} */
      this[_LastModified] = DatePrototypeGetTime(new Date());
    } else {
      /** @type {number} */
      this[_LastModified] = options.lastModified;
    }
  }

  /** @returns {string} */
  get name() {
    webidl.assertBranded(this, FilePrototype);
    return this[_Name];
  }

  /** @returns {number} */
  get lastModified() {
    webidl.assertBranded(this, FilePrototype);
    return this[_LastModified];
  }

  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    return inspect(
      createFilteredInspectProxy({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(FilePrototype, this),
        keys: [
          "name",
          "size",
          "type",
        ],
      }),
      inspectOptions,
    );
  }
}

webidl.configureInterface(File);
const FilePrototype = File.prototype;

webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter(
  "FilePropertyBag",
  blobPropertyBagDictionary,
  [
    {
      key: "lastModified",
      converter: webidl.converters["long long"],
    },
  ],
);

// A finalization registry to deallocate a blob part when its JS reference is
// garbage collected.
const registry = new SafeFinalizationRegistry((uuid) => {
  op_blob_remove_part(uuid);
});

// TODO(lucacasonato): get a better stream from Rust in BlobReference#stream

/**
 * An opaque reference to a blob part in Rust. This could be backed by a file,
 * in memory storage, or something else.
 */
class BlobReference {
  /**
   * Don't use directly. Use `BlobReference.fromUint8Array`.
   * @param {string} id
   * @param {number} size
   */
  constructor(id, size) {
    this._id = id;
    this.size = size;
    registry.register(this, id);
  }

  /**
   * Create a new blob part from a Uint8Array.
   *
   * @param {Uint8Array} data
   * @returns {BlobReference}
   */
  static fromUint8Array(data) {
    const id = op_blob_create_part(data);
    return new BlobReference(id, TypedArrayPrototypeGetByteLength(data));
  }

  /**
   * Create a new BlobReference by slicing this BlobReference. This is a copy
   * free operation - the sliced reference will still reference the original
   * underlying bytes.
   *
   * @param {number} start
   * @param {number} end
   * @returns {BlobReference}
   */
  slice(start, end) {
    const size = end - start;
    const id = op_blob_slice_part(this._id, {
      start,
      len: size,
    });
    return new BlobReference(id, size);
  }

  /**
   * Read the entire contents of the reference blob.
   * @returns {AsyncGenerator<Uint8Array>}
   */
  async *stream() {
    yield op_blob_read_part(this._id);

    // let position = 0;
    // const end = this.size;
    // while (position !== end) {
    //   const size = MathMin(end - position, 65536);
    //   const chunk = this.slice(position, position + size);
    //   position += chunk.size;
    //   yield op_blob_read_part( chunk._id);
    // }
  }
}

/**
 * Get all Parts as a flat array of BlobReference objects.
 * @param {Blob} blob
 * @param {BlobReference[]} bag
 * @returns {BlobReference[]}
 */
function getPartRefs(blob, bag = []) {
  const parts = blob[_parts];
  for (let i = 0; i < parts.length; ++i) {
    const part = parts[i];
    if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) {
      getPartRefs(part, bag);
    } else {
      ArrayPrototypePush(bag, part);
    }
  }
  return bag;
}

/**
 * Clone blob part references in BlobStore and return serializable metadata.
 * @param {Blob} blob
 * @returns {{ uuid: string, size: number }[]}
 */
function cloneBlobParts(blob) {
  if (blob[_fileBacked]) {
    throw new TypeError("Invalid state: File-backed Blobs are not cloneable");
  }
  const refs = getPartRefs(blob);
  const cloned = [];
  for (let i = 0; i < refs.length; ++i) {
    ArrayPrototypePush(cloned, op_blob_clone_part(refs[i]._id));
  }
  return cloned;
}

ObjectDefineProperty(Blob.prototype, core.hostObjectBrand, {
  __proto__: null,
  value: function () {
    return {
      type: "Blob",
      mimeType: this[_type],
      parts: cloneBlobParts(this),
      size: this[_size],
    };
  },
  enumerable: false,
  configurable: false,
  writable: false,
});

core.registerCloneableResource("Blob", (data) => {
  const parts = [];
  for (let i = 0; i < data.parts.length; ++i) {
    const { uuid, size } = data.parts[i];
    ArrayPrototypePush(parts, new BlobReference(uuid, size));
  }
  const blob = new Blob();
  blob[_type] = data.mimeType;
  blob[_size] = data.size;
  blob[_parts] = parts;
  return blob;
});

/**
 * Mark a Blob as backed by file storage. File-backed Blobs are intentionally
 * rejected by the structured clone serializer, matching Node's behavior.
 * @param {Blob} blob
 * @returns {Blob}
 */
function markFileBackedBlob(blob) {
  webidl.assertBranded(blob, BlobPrototype);
  blob[_fileBacked] = true;
  return blob;
}

ObjectDefineProperty(File.prototype, core.hostObjectBrand, {
  __proto__: null,
  value: function () {
    return {
      type: "File",
      mimeType: this[_type],
      parts: cloneBlobParts(this),
      size: this[_size],
      name: this[_Name],
      lastModified: this[_LastModified],
    };
  },
  enumerable: false,
  configurable: false,
  writable: false,
});

core.registerCloneableResource("File", (data) => {
  const parts = [];
  for (let i = 0; i < data.parts.length; ++i) {
    const { uuid, size } = data.parts[i];
    ArrayPrototypePush(parts, new BlobReference(uuid, size));
  }
  const file = new File([], data.name, {
    type: data.mimeType,
    lastModified: data.lastModified,
  });
  file[_size] = data.size;
  file[_parts] = parts;
  return file;
});

/**
 * Construct a new Blob object from an object URL.
 *
 * This new object will not duplicate data in memory with the original Blob
 * object from which this URL was created or with other Blob objects created
 * from the same URL, but they will be different objects.
 *
 * The object returned from this function will not be a File object, even if
 * the original object from which the object URL was constructed was one. This
 * means that the `name` and `lastModified` properties are lost.
 *
 * @param {string} url
 * @returns {Blob | null}
 */
function blobFromObjectUrl(url) {
  const blobData = op_blob_from_object_url(url);
  if (blobData === null) {
    return null;
  }

  /** @type {BlobReference[]} */
  const parts = [];
  let totalSize = 0;

  for (let i = 0; i < blobData.parts.length; ++i) {
    const { uuid, size } = blobData.parts[i];
    ArrayPrototypePush(parts, new BlobReference(uuid, size));
    totalSize += size;
  }

  const blob = new Blob();
  blob[_type] = blobData.mediaType;
  blob[_size] = totalSize;
  blob[_parts] = parts;
  return blob;
}

/**
 * @param {Blob} blob
 * @returns {string}
 */
function createObjectURL(blob) {
  const prefix = "Failed to execute 'createObjectURL' on 'URL'";
  webidl.requiredArguments(arguments.length, 1, prefix);
  if (!isBlob(blob)) {
    // Node.js throws ERR_INVALID_ARG_TYPE for non-Blob arguments; preserve
    // that `code` while still throwing a TypeError as the web platform does.
    const err = new TypeError(
      `${prefix}: The "blob" argument must be an instance of Blob`,
    );
    err.code = "ERR_INVALID_ARG_TYPE";
    throw err;
  }
  blob = webidl.converters["Blob"](blob, prefix, "Argument 1");

  return op_blob_create_object_url(blob.type, getParts(blob));
}

/**
 * @param {string} url
 * @returns {void}
 */
function revokeObjectURL(url) {
  const prefix = "Failed to execute 'revokeObjectURL' on 'URL'";
  if (arguments.length < 1) {
    const err = new TypeError(
      `${prefix}: 1 argument required, but only 0 present`,
    );
    err.code = "ERR_MISSING_ARGS";
    throw err;
  }
  url = webidl.converters["DOMString"](url, prefix, "Argument 1");

  op_blob_revoke_object_url(url);
}

URL.createObjectURL = createObjectURL;
URL.revokeObjectURL = revokeObjectURL;

function isBlob(obj) {
  return ObjectPrototypeIsPrototypeOf(BlobPrototype, obj);
}

return {
  Blob,
  blobFromObjectUrl,
  BlobPrototype,
  File,
  FilePrototype,
  getParts,
  isBlob,
  markFileBackedBlob,
};
})();
