text: "use strict";
  
  const HTTP_STATUS_CODES = require("http").STATUS_CODES;
  const { spawnSync } = require("child_process");
  const { URL } = require("whatwg-url");
  const whatwgEncoding = require("whatwg-encoding");
  const tough = require("tough-cookie");
  const MIMEType = require("whatwg-mimetype");
  const conversions = require("webidl-conversions");
  
  const xhrUtils = require("./xhr-utils");
  const DOMException = require("domexception");
  const xhrSymbols = require("./xmlhttprequest-symbols");
  const { addConstants } = require("../utils");
  const { documentBaseURLSerialized } = require("./helpers/document-base-url");
  const { asciiCaseInsensitiveMatch } = require("./helpers/strings");
  const idlUtils = require("./generated/utils");
  const Document = require("./generated/Document");
  const Blob = require("./generated/Blob");
  const FormData = require("./generated/FormData");
  const XMLHttpRequestEventTarget = require("./generated/XMLHttpRequestEventTarget");
  const XMLHttpRequestUpload = require("./generated/XMLHttpRequestUpload");
  const ProgressEvent = require("./generated/ProgressEvent");
  const { isArrayBuffer } = require("./generated/utils");
  const { parseIntoDocument } = require("../browser/parser");
  const { fragmentSerialization } = require("./domparsing/serialization");
  const { setupForSimpleEventAccessors } = require("./helpers/create-event-accessor");
  const { parseJSONFromBytes } = require("./helpers/json");
  const { fireAnEvent } = require("./helpers/events");
  const { copyToArrayBufferInNewRealm } = require("./helpers/binary-data");
  
  const syncWorkerFile = require.resolve ? require.resolve("./xhr-sync-worker.js") : null;
  
  const tokenRegexp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
  const fieldValueRegexp = /^[ \t]*(?:[\x21-\x7E\x80-\xFF](?:[ \t][\x21-\x7E\x80-\xFF])?)*[ \t]*$/;
  
  const forbiddenRequestHeaders = new Set([
    "accept-charset",
    "accept-encoding",
    "access-control-request-headers",
    "access-control-request-method",
    "connection",
    "content-length",
    "cookie",
    "cookie2",
    "date",
    "dnt",
    "expect",
    "host",
    "keep-alive",
    "origin",
    "referer",
    "te",
    "trailer",
    "transfer-encoding",
    "upgrade",
    "via"
  ]);
  const forbiddenResponseHeaders = new Set([
    "set-cookie",
    "set-cookie2"
  ]);
  const uniqueResponseHeaders = new Set([
    "content-type",
    "content-length",
    "user-agent",
    "referer",
    "host",
    "authorization",
    "proxy-authorization",
    "if-modified-since",
    "if-unmodified-since",
    "from",
    "location",
    "max-forwards"
  ]);
  const corsSafeResponseHeaders = new Set([
    "cache-control",
    "content-language",
    "content-type",
    "expires",
    "last-modified",
    "pragma"
  ]);
  
  
  const allowedRequestMethods = new Set(["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE"]);
  const forbiddenRequestMethods = new Set(["TRACK", "TRACE", "CONNECT"]);
  
  const XMLHttpRequestResponseType = new Set([
    "",
    "arraybuffer",
    "blob",
    "document",
    "json",
    "text"
  ]);
  
  module.exports = function createXMLHttpRequest(window) {
    // TODO: Wrapping the XMLHttpRequest interface into a factory is not needed anymore with
    // the webidl2js constructor reform. This code can be cleaned up.
    class XMLHttpRequest extends window.XMLHttpRequestEventTarget {
      constructor() { // eslint-disable-line constructor-super
        const theThis = Object.create(new.target.prototype);
        theThis._globalObject = window;
        XMLHttpRequestEventTarget.setup(theThis, [], { globalObject: window });
        theThis.upload = XMLHttpRequestUpload.create(window);
        theThis.upload._ownerDocument = window.document;
  
        theThis[xhrSymbols.flag] = {
          synchronous: false,
          withCredentials: false,
          mimeType: null,
          auth: null,
          method: undefined,
          responseType: "",
          requestHeaders: {},
          referrer: theThis._ownerDocument.URL,
          uri: "",
          timeout: 0,
          body: undefined,
          formData: false,
          preflight: false,
          requestManager: theThis._ownerDocument._requestManager,
          strictSSL: window._resourceLoader._strictSSL,
          proxy: window._resourceLoader._proxy,
          cookieJar: theThis._ownerDocument._cookieJar,
          encoding: theThis._ownerDocument._encoding,
          origin: theThis._ownerDocument.origin,
          userAgent: window.navigator.userAgent
        };
  
        theThis[xhrSymbols.properties] = {
          beforeSend: false,
          send: false,
          timeoutStart: 0,
          timeoutId: 0,
          timeoutFn: null,
          client: null,
          responseHeaders: {},
          filteredResponseHeaders: [],
          responseBuffer: null,
          responseCache: null,
          responseTextCache: null,
          responseXMLCache: null,
          responseURL: "",
          readyState: XMLHttpRequest.UNSENT,
          status: 0,
          statusText: "",
          error: "",
          uploadComplete: false,
          uploadListener: false,
  
          // Signifies that we're calling abort() from xhr-utils.js because of a window shutdown.
          // In that case the termination reason is "fatal", not "end-user abort".
          abortError: false,
  
          cookieJar: theThis._ownerDocument._cookieJar,
          bufferStepSize: 1 * 1024 * 1024, // pre-allocate buffer increase step size. init value is 1MB
          totalReceivedChunkSize: 0
        };
  
        return theThis;
      }
  
      get readyState() {
        return this[xhrSymbols.properties].readyState;
      }
      get status() {
        return this[xhrSymbols.properties].status;
      }
      get statusText() {
        return this[xhrSymbols.properties].statusText;
      }
      get responseType() {
        return this[xhrSymbols.flag].responseType;
      }
      set responseType(responseType) {
        const flag = this[xhrSymbols.flag];
        if (this.readyState === XMLHttpRequest.LOADING || this.readyState === XMLHttpRequest.DONE) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        if (this.readyState === XMLHttpRequest.OPENED && flag.synchronous) {
          throw new DOMException("The object does not support the operation or argument.", "InvalidAccessError");
        }
        if (!XMLHttpRequestResponseType.has(responseType)) {
          responseType = "";
        }
        flag.responseType = responseType;
      }
      get response() {
        const properties = this[xhrSymbols.properties];
        if (properties.responseCache) {
          return properties.responseCache;
        }
        let res = "";
  
        const responseBuffer = properties.responseBuffer ?
                               properties.responseBuffer.slice(0, properties.totalReceivedChunkSize) :
                               null;
  
        switch (this.responseType) {
          case "":
          case "text": {
            res = this.responseText;
            break;
          }
          case "arraybuffer": {
            if (!responseBuffer) {
              return null;
            }
            res = copyToArrayBufferInNewRealm(responseBuffer, this._globalObject);
            break;
          }
          case "blob": {
            if (!responseBuffer) {
              return null;
            }
            const contentType = finalMIMEType(this);
            res = Blob.create(this._globalObject, [
              [new Uint8Array(responseBuffer)],
              { type: contentType || "" }
            ]);
            break;
          }
          case "document": {
            res = this.responseXML;
            break;
          }
          case "json": {
            if (this.readyState !== XMLHttpRequest.DONE || !responseBuffer) {
              res = null;
            }
  
            try {
              res = parseJSONFromBytes(responseBuffer);
            } catch (e) {
              res = null;
            }
            break;
          }
        }
        properties.responseCache = res;
        return res;
      }
      get responseText() {
        const properties = this[xhrSymbols.properties];
        if (this.responseType !== "" && this.responseType !== "text") {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        if (this.readyState !== XMLHttpRequest.LOADING && this.readyState !== XMLHttpRequest.DONE) {
          return "";
        }
        if (properties.responseTextCache) {
          return properties.responseTextCache;
        }
        const responseBuffer = properties.responseBuffer ?
                               properties.responseBuffer.slice(0, properties.totalReceivedChunkSize) :
                               null;
  
        if (!responseBuffer) {
          return "";
        }
  
        const fallbackEncoding = finalCharset(this) || whatwgEncoding.getBOMEncoding(responseBuffer) || "UTF-8";
        const res = whatwgEncoding.decode(responseBuffer, fallbackEncoding);
  
        properties.responseTextCache = res;
        return res;
      }
      get responseXML() {
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
        if (this.responseType !== "" && this.responseType !== "document") {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        if (this.readyState !== XMLHttpRequest.DONE) {
          return null;
        }
        if (properties.responseXMLCache) {
          return properties.responseXMLCache;
        }
        const responseBuffer = properties.responseBuffer ?
                               properties.responseBuffer.slice(0, properties.totalReceivedChunkSize) :
                               null;
  
        if (!responseBuffer) {
          return null;
        }
  
        const contentType = finalMIMEType(this);
        let isHTML = false;
        let isXML = false;
        const parsed = MIMEType.parse(contentType);
        if (parsed) {
          isHTML = parsed.isHTML();
          isXML = parsed.isXML();
          if (!isXML && !isHTML) {
            return null;
          }
        }
  
        if (this.responseType === "" && isHTML) {
          return null;
        }
  
        const encoding = finalCharset(this) || whatwgEncoding.getBOMEncoding(responseBuffer) || "UTF-8";
        const resText = whatwgEncoding.decode(responseBuffer, encoding);
  
        if (!resText) {
          return null;
        }
        const res = Document.create(this._globalObject, [], {
          options: {
            url: flag.uri,
            lastModified: new Date(getResponseHeader(this, "last-modified")),
            parsingMode: isHTML ? "html" : "xml",
            cookieJar: { setCookieSync: () => undefined, getCookieStringSync: () => "" },
            encoding,
            parseOptions: this._ownerDocument._parseOptions
          }
        });
        const resImpl = idlUtils.implForWrapper(res);
        try {
          parseIntoDocument(resText, resImpl);
        } catch (e) {
          properties.responseXMLCache = null;
          return null;
        }
        res.close();
        properties.responseXMLCache = res;
        return res;
      }
  
      get responseURL() {
        return this[xhrSymbols.properties].responseURL;
      }
  
      get timeout() {
        return this[xhrSymbols.flag].timeout;
      }
      set timeout(val) {
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
        if (flag.synchronous) {
          throw new DOMException("The object does not support the operation or argument.", "InvalidAccessError");
        }
        flag.timeout = val;
        clearTimeout(properties.timeoutId);
        if (val > 0 && properties.timeoutFn) {
          properties.timeoutId = setTimeout(
            properties.timeoutFn,
            Math.max(0, val - ((new Date()).getTime() - properties.timeoutStart))
          );
        } else {
          properties.timeoutFn = null;
          properties.timeoutStart = 0;
        }
      }
      get withCredentials() {
        return this[xhrSymbols.flag].withCredentials;
      }
      set withCredentials(val) {
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
        if (!(this.readyState === XMLHttpRequest.UNSENT || this.readyState === XMLHttpRequest.OPENED)) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        if (properties.send) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        flag.withCredentials = val;
      }
  
      abort() {
        const properties = this[xhrSymbols.properties];
  
        // Terminate the request
        clearTimeout(properties.timeoutId);
        properties.timeoutFn = null;
        properties.timeoutStart = 0;
  
        const { client } = properties;
        if (client) {
          client.abort();
          properties.client = null;
        }
  
        if (properties.abortError) {
          // Special case that ideally shouldn't be going through the public API at all.
          // Run the https://xhr.spec.whatwg.org/#handle-errors "fatal" steps.
          properties.readyState = XMLHttpRequest.DONE;
          properties.send = false;
          xhrUtils.setResponseToNetworkError(this);
          return;
        }
  
        if ((this.readyState === XMLHttpRequest.OPENED && properties.send) ||
            this.readyState === XMLHttpRequest.HEADERS_RECEIVED ||
            this.readyState === XMLHttpRequest.LOADING) {
          xhrUtils.requestErrorSteps(this, "abort");
        }
  
        if (this.readyState === XMLHttpRequest.DONE) {
          properties.readyState = XMLHttpRequest.UNSENT;
  
          xhrUtils.setResponseToNetworkError(this);
        }
      }
      getAllResponseHeaders() {
        const properties = this[xhrSymbols.properties];
        const { readyState } = this;
        if (readyState === XMLHttpRequest.UNSENT || readyState === XMLHttpRequest.OPENED) {
          return "";
        }
        return Object.keys(properties.responseHeaders)
          .filter(key => properties.filteredResponseHeaders.indexOf(key) === -1)
          .map(key => [conversions.ByteString(key).toLowerCase(), properties.responseHeaders[key]].join(": "))
          .join("\r\n");
      }
  
      getResponseHeader(header) {
        const properties = this[xhrSymbols.properties];
        const { readyState } = this;
        if (readyState === XMLHttpRequest.UNSENT || readyState === XMLHttpRequest.OPENED) {
          return null;
        }
        const lcHeader = conversions.ByteString(header).toLowerCase();
        if (properties.filteredResponseHeaders.find(filtered => lcHeader === filtered.toLowerCase())) {
          return null;
        }
        return getResponseHeader(this, lcHeader);
      }
  
      open(method, uri, asynchronous, user, password) {
        if (!this._ownerDocument) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
        const argumentCount = arguments.length;
        if (argumentCount < 2) {
          throw new TypeError("Not enough arguments (expected at least 2)");
        }
  
        method = conversions.ByteString(method);
        uri = conversions.USVString(uri);
        if (user) {
          user = conversions.USVString(user);
        }
        if (password) {
          password = conversions.USVString(password);
        }
  
        if (!tokenRegexp.test(method)) {
          throw new DOMException("The string did not match the expected pattern.", "SyntaxError");
        }
        const upperCaseMethod = method.toUpperCase();
        if (forbiddenRequestMethods.has(upperCaseMethod)) {
          throw new DOMException("The operation is insecure.", "SecurityError");
        }
  
        const { client } = properties;
        if (client && typeof client.abort === "function") {
          client.abort();
        }
  
        if (allowedRequestMethods.has(upperCaseMethod)) {
          method = upperCaseMethod;
        }
        if (typeof asynchronous !== "undefined") {
          flag.synchronous = !asynchronous;
        } else {
          flag.synchronous = false;
        }
        if (flag.responseType && flag.synchronous) {
          throw new DOMException("The object does not support the operation or argument.", "InvalidAccessError");
        }
        if (flag.synchronous && flag.timeout) {
          throw new DOMException("The object does not support the operation or argument.", "InvalidAccessError");
        }
        flag.method = method;
  
        let urlObj;
        try {
          urlObj = new URL(uri, documentBaseURLSerialized(this._ownerDocument));
        } catch (e) {
          throw new DOMException("The string did not match the expected pattern.", "SyntaxError");
        }
  
        if (user || (password && !urlObj.username)) {
          flag.auth = {
            user,
            pass: password
          };
          urlObj.username = "";
          urlObj.password = "";
        }
  
        flag.uri = urlObj.href;
        flag.requestHeaders = {};
        flag.preflight = false;
  
        properties.send = false;
        properties.uploadListener = false;
        properties.requestBuffer = null;
        properties.requestCache = null;
        properties.abortError = false;
        properties.responseURL = "";
        readyStateChange(this, XMLHttpRequest.OPENED);
      }
  
      overrideMimeType(mime) {
        mime = String(mime);
  
        const { readyState } = this;
        if (readyState === XMLHttpRequest.LOADING || readyState === XMLHttpRequest.DONE) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
  
        this[xhrSymbols.flag].overrideMIMEType = "application/octet-stream";
  
        // Waiting for better spec: https://github.com/whatwg/xhr/issues/157
        const parsed = MIMEType.parse(mime);
        if (parsed) {
          this[xhrSymbols.flag].overrideMIMEType = parsed.essence;
  
          const charset = parsed.parameters.get("charset");
          if (charset) {
            this[xhrSymbols.flag].overrideCharset = whatwgEncoding.labelToName(charset);
          }
        }
      }
  
      send(body) {
        body = coerceBodyArg(body);
  
        // Not per spec, but per tests: https://github.com/whatwg/xhr/issues/65
        if (!this._ownerDocument) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
  
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
  
        if (this.readyState !== XMLHttpRequest.OPENED || properties.send) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
  
        properties.beforeSend = true;
  
        try {
          if (flag.method === "GET" || flag.method === "HEAD") {
            body = null;
          }
  
          if (body !== null) {
            let encoding = null;
            let mimeType = null;
  
            if (Document.isImpl(body)) {
              encoding = "UTF-8";
              mimeType = (body._parsingMode === "html" ? "text/html" : "application/xml") + ";charset=UTF-8";
              flag.body = fragmentSerialization(body, { requireWellFormed: false });
            } else {
              if (typeof body === "string") {
                encoding = "UTF-8";
              }
              const { buffer, formData, contentType } = extractBody(body);
              mimeType = contentType;
              flag.body = buffer || formData;
              flag.formData = Boolean(formData);
            }
  
            const existingContentType = xhrUtils.getRequestHeader(flag.requestHeaders, "content-type");
            if (mimeType !== null && existingContentType === null) {
              flag.requestHeaders["Content-Type"] = mimeType;
            } else if (existingContentType !== null && encoding !== null) {
              // Waiting for better spec: https://github.com/whatwg/xhr/issues/188. This seems like a good guess at what
              // the spec will be, in the meantime.
              const parsed = MIMEType.parse(existingContentType);
              if (parsed) {
                const charset = parsed.parameters.get("charset");
                if (charset && !asciiCaseInsensitiveMatch(charset, encoding) && encoding !== null) {
                  parsed.parameters.set("charset", encoding);
                  xhrUtils.updateRequestHeader(flag.requestHeaders, "content-type", parsed.toString());
                }
              }
            }
          }
        } finally {
          if (properties.beforeSend) {
            properties.beforeSend = false;
          } else {
            throw new DOMException("The object is in an invalid state.", "InvalidStateError");
          }
        }
  
        if (Object.keys(idlUtils.implForWrapper(this.upload)._eventListeners).length > 0) {
          properties.uploadListener = true;
        }
  
        // request doesn't like zero-length bodies
        if (flag.body && flag.body.byteLength === 0) {
          flag.body = null;
        }
  
        if (flag.synchronous) {
          const flagStr = JSON.stringify(flag, function (k, v) {
            if (this === flag && k === "requestManager") {
              return null;
            }
            if (this === flag && k === "pool" && v) {
              return { maxSockets: v.maxSockets };
            }
            return v;
          });
          const res = spawnSync(
            process.execPath,
            [syncWorkerFile],
            { input: flagStr, maxBuffer: Infinity }
          );
          if (res.status !== 0) {
            throw new Error(res.stderr.toString());
          }
          if (res.error) {
            if (typeof res.error === "string") {
              res.error = new Error(res.error);
            }
            throw res.error;
          }
  
          const response = JSON.parse(res.stdout.toString());
          if (response.properties.responseBuffer && response.properties.responseBuffer.data) {
            response.properties.responseBuffer = Buffer.from(response.properties.responseBuffer.data);
          }
          if (response.properties.cookieJar) {
            response.properties.cookieJar = tough.CookieJar.deserializeSync(
              response.properties.cookieJar,
              this._ownerDocument._cookieJar.store
            );
          }
  
          response.properties.readyState = XMLHttpRequest.LOADING;
          this[xhrSymbols.properties] = response.properties;
  
          if (response.properties.error) {
            xhrUtils.dispatchError(this);
            throw new DOMException(response.properties.error, "NetworkError");
          } else {
            const { responseBuffer } = this[xhrSymbols.properties];
            const contentLength = getResponseHeader(this, "content-length") || "0";
            const bufferLength = parseInt(contentLength) || responseBuffer.length;
            const progressObj = { lengthComputable: false };
            if (bufferLength !== 0) {
              progressObj.total = bufferLength;
              progressObj.loaded = bufferLength;
              progressObj.lengthComputable = true;
            }
            fireAnEvent("progress", this, ProgressEvent, progressObj);
            readyStateChange(this, XMLHttpRequest.DONE);
            fireAnEvent("load", this, ProgressEvent, progressObj);
            fireAnEvent("loadend", this, ProgressEvent, progressObj);
          }
        } else {
          properties.send = true;
  
          fireAnEvent("loadstart", this, ProgressEvent);
  
          const client = xhrUtils.createClient(this);
  
          properties.client = client;
          // For new client, reset totalReceivedChunkSize and bufferStepSize
          properties.totalReceivedChunkSize = 0;
          properties.bufferStepSize = 1 * 1024 * 1024;
  
          properties.origin = flag.origin;
  
          client.on("error", err => {
            client.removeAllListeners();
            properties.error = err;
            xhrUtils.dispatchError(this);
          });
  
          client.on("response", res => receiveResponse(this, res));
  
          client.on("redirect", () => {
            const { response } = client;
            const destUrlObj = new URL(response.request.headers.Referer);
  
            const urlObj = new URL(response.request.uri.href);
  
            if (destUrlObj.origin !== urlObj.origin && destUrlObj.origin !== flag.origin) {
              properties.origin = "null";
            }
  
            response.request.headers.Origin = properties.origin;
  
            if (flag.origin !== destUrlObj.origin &&
                destUrlObj.protocol !== "data:") {
              if (!xhrUtils.validCORSHeaders(this, response, flag, properties, flag.origin)) {
                return;
              }
              if (urlObj.username || urlObj.password) {
                properties.error = "Userinfo forbidden in cors redirect";
                xhrUtils.dispatchError(this);
              }
            }
          });
          if (body !== null && body !== "") {
            properties.uploadComplete = false;
            setDispatchProgressEvents(this);
          } else {
            properties.uploadComplete = true;
          }
          if (this.timeout > 0) {
            properties.timeoutStart = (new Date()).getTime();
            properties.timeoutFn = () => {
              client.abort();
              if (!(this.readyState === XMLHttpRequest.UNSENT ||
                  (this.readyState === XMLHttpRequest.OPENED && !properties.send) ||
                  this.readyState === XMLHttpRequest.DONE)) {
                properties.send = false;
                let stateChanged = false;
                if (!properties.uploadComplete) {
                  const uploadImpl = idlUtils.implForWrapper(this.upload);
                  fireAnEvent("progress", uploadImpl, ProgressEvent);
                  readyStateChange(this, XMLHttpRequest.DONE);
                  fireAnEvent("timeout", uploadImpl, ProgressEvent);
                  fireAnEvent("loadend", uploadImpl, ProgressEvent);
                  stateChanged = true;
                }
                fireAnEvent("progress", this, ProgressEvent);
                if (!stateChanged) {
                  readyStateChange(this, XMLHttpRequest.DONE);
                }
                fireAnEvent("timeout", this, ProgressEvent);
                fireAnEvent("loadend", this, ProgressEvent);
              }
              properties.readyState = XMLHttpRequest.UNSENT;
            };
            properties.timeoutId = setTimeout(properties.timeoutFn, this.timeout);
          }
        }
      }
  
      setRequestHeader(header, value) {
        const flag = this[xhrSymbols.flag];
        const properties = this[xhrSymbols.properties];
  
        if (arguments.length !== 2) {
          throw new TypeError("2 arguments required for setRequestHeader");
        }
        header = conversions.ByteString(header);
        value = conversions.ByteString(value);
  
        if (this.readyState !== XMLHttpRequest.OPENED || properties.send) {
          throw new DOMException("The object is in an invalid state.", "InvalidStateError");
        }
  
        value = normalizeHeaderValue(value);
  
        if (!tokenRegexp.test(header) || !fieldValueRegexp.test(value)) {
          throw new DOMException("The string did not match the expected pattern.", "SyntaxError");
        }
  
        const lcHeader = header.toLowerCase();
  
        if (forbiddenRequestHeaders.has(lcHeader) || lcHeader.startsWith("sec-") || lcHeader.startsWith("proxy-")) {
          return;
        }
  
        const keys = Object.keys(flag.requestHeaders);
        let n = keys.length;
        while (n--) {
          const key = keys[n];
          if (key.toLowerCase() === lcHeader) {
            flag.requestHeaders[key] += ", " + value;
            return;
          }
        }
        flag.requestHeaders[header] = value;
      }
  
      get _ownerDocument() {
        return idlUtils.implForWrapper(window.document);
      }
    }
  
    Object.defineProperty(XMLHttpRequest.prototype, Symbol.toStringTag, {
      value: "XMLHttpRequest",
      writable: false,
      enumerable: false,
      configurable: true
    });
  
    setupForSimpleEventAccessors(XMLHttpRequest.prototype, ["readystatechange"]);
  
    addConstants(XMLHttpRequest, {
      UNSENT: 0,
      OPENED: 1,
      HEADERS_RECEIVED: 2,
      LOADING: 3,
      DONE: 4
    });
  
    function readyStateChange(xhr, readyState) {
      const properties = xhr[xhrSymbols.properties];
      if (properties.readyState === readyState) {
        return;
      }
  
      properties.readyState = readyState;
  
      fireAnEvent("readystatechange", xhr);
    }
  
    function receiveResponse(xhr, response) {
      const properties = xhr[xhrSymbols.properties];
      const flag = xhr[xhrSymbols.flag];
  
      const { statusCode } = response;
  
      let byteOffset = 0;
  
      const headers = {};
      const filteredResponseHeaders = [];
      const headerMap = {};
      const { rawHeaders } = response;
      const n = Number(rawHeaders.length);
      for (let i = 0; i < n; i += 2) {
        const k = rawHeaders[i];
        const kl = k.toLowerCase();
        const v = rawHeaders[i + 1];
        if (uniqueResponseHeaders.has(kl)) {
          if (headerMap[kl] !== undefined) {
            delete headers[headerMap[kl]];
          }
          headers[k] = v;
        } else if (headerMap[kl] !== undefined) {
          headers[headerMap[kl]] += ", " + v;
        } else {
          headers[k] = v;
        }
        headerMap[kl] = k;
      }
  
      const destUrlObj = new URL(response.request.uri.href);
      if (properties.origin !== destUrlObj.origin &&
          destUrlObj.protocol !== "data:") {
        if (!xhrUtils.validCORSHeaders(xhr, response, flag, properties, properties.origin)) {
          return;
        }
        const acehStr = response.headers["access-control-expose-headers"];
        const aceh = new Set(acehStr ? acehStr.trim().toLowerCase().split(xhrUtils.headerListSeparatorRegexp) : []);
        for (const header in headers) {
          const lcHeader = header.toLowerCase();
          if (!corsSafeResponseHeaders.has(lcHeader) && !aceh.has(lcHeader)) {
            filteredResponseHeaders.push(header);
          }
        }
      }
  
      for (const header in headers) {
        const lcHeader = header.toLowerCase();
        if (forbiddenResponseHeaders.has(lcHeader)) {
          filteredResponseHeaders.push(header);
        }
      }
  
      properties.responseURL = destUrlObj.href;
  
      properties.status = statusCode;
      properties.statusText = response.statusMessage || HTTP_STATUS_CODES[statusCode] || "";
  
      properties.responseHeaders = headers;
      properties.filteredResponseHeaders = filteredResponseHeaders;
  
      const contentLength = getResponseHeader(xhr, "content-length") || "0";
      const bufferLength = parseInt(contentLength) || 0;
      const progressObj = { lengthComputable: false };
      let lastProgressReported;
      if (bufferLength !== 0) {
        progressObj.total = bufferLength;
        progressObj.loaded = 0;
        progressObj.lengthComputable = true;
      }
      // pre-allocate buffer.
      properties.responseBuffer = Buffer.alloc(properties.bufferStepSize);
      properties.responseCache = null;
      properties.responseTextCache = null;
      properties.responseXMLCache = null;
      readyStateChange(xhr, XMLHttpRequest.HEADERS_RECEIVED);
  
      if (!properties.client) {
        // The request was aborted in reaction to the readystatechange event.
        return;
      }
  
      // Can't use the client since the client gets the post-ungzipping bytes (which can be greater than the
      // Content-Length).
      response.on("data", chunk => {
        byteOffset += chunk.length;
        progressObj.loaded = byteOffset;
      });
  
      properties.client.on("data", chunk => {
        properties.totalReceivedChunkSize += chunk.length;
        if (properties.totalReceivedChunkSize >= properties.bufferStepSize) {
          properties.bufferStepSize *= 2;
          while (properties.totalReceivedChunkSize >= properties.bufferStepSize) {
            properties.bufferStepSize *= 2;
          }
          const tmpBuf = Buffer.alloc(properties.bufferStepSize);
          properties.responseBuffer.copy(tmpBuf, 0, 0, properties.responseBuffer.length);
          properties.responseBuffer = tmpBuf;
        }
        chunk.copy(properties.responseBuffer, properties.totalReceivedChunkSize - chunk.length, 0, chunk.length);
        properties.responseCache = null;
        properties.responseTextCache = null;
        properties.responseXMLCache = null;
  
        if (properties.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
          properties.readyState = XMLHttpRequest.LOADING;
        }
        fireAnEvent("readystatechange", xhr);
  
        if (progressObj.total !== progressObj.loaded || properties.totalReceivedChunkSize === byteOffset) {
          if (lastProgressReported !== progressObj.loaded) {
            // This is a necessary check in the gzip case where we can be getting new data from the client, as it
            // un-gzips, but no new data has been gotten from the response, so we should not fire a progress event.
            lastProgressReported = progressObj.loaded;
            fireAnEvent("progress", xhr, ProgressEvent, progressObj);
          }
        }
      });
      properties.client.on("end", () => {
        clearTimeout(properties.timeoutId);
        properties.timeoutFn = null;
        properties.timeoutStart = 0;
        properties.client = null;
        fireAnEvent("progress", xhr, ProgressEvent, progressObj);
        readyStateChange(xhr, XMLHttpRequest.DONE);
        fireAnEvent("load", xhr, ProgressEvent, progressObj);
        fireAnEvent("loadend", xhr, ProgressEvent, progressObj);
      });
    }
  
    function setDispatchProgressEvents(xhr) {
      const properties = xhr[xhrSymbols.properties];
      const { client } = properties;
      const uploadImpl = idlUtils.implForWrapper(xhr.upload);
  
      let total = 0;
      let lengthComputable = false;
      const length = client.headers && parseInt(xhrUtils.getRequestHeader(client.headers, "content-length"));
      if (length) {
        total = length;
        lengthComputable = true;
      }
      const initProgress = {
        lengthComputable,
        total,
        loaded: 0
      };
  
      if (properties.uploadListener) {
        fireAnEvent("loadstart", uploadImpl, ProgressEvent, initProgress);
      }
  
      client.on("request", req => {
        req.on("response", () => {
          properties.uploadComplete = true;
  
          if (!properties.uploadListener) {
            return;
          }
  
          const progress = {
            lengthComputable,
            total,
            loaded: total
          };
          fireAnEvent("progress", uploadImpl, ProgressEvent, progress);
          fireAnEvent("load", uploadImpl, ProgressEvent, progress);
          fireAnEvent("loadend", uploadImpl, ProgressEvent, progress);
        });
      });
    }
  
    return XMLHttpRequest;
  };
  
  function finalMIMEType(xhr) {
    const flag = xhr[xhrSymbols.flag];
    return flag.overrideMIMEType || getResponseHeader(xhr, "content-type");
  }
  
  function finalCharset(xhr) {
    const flag = xhr[xhrSymbols.flag];
    if (flag.overrideCharset) {
      return flag.overrideCharset;
    }
    const parsedContentType = MIMEType.parse(getResponseHeader(xhr, "content-type"));
    if (parsedContentType) {
      return whatwgEncoding.labelToName(parsedContentType.parameters.get("charset"));
    }
    return null;
  }
  
  function getResponseHeader(xhr, lcHeader) {
    const properties = xhr[xhrSymbols.properties];
    const keys = Object.keys(properties.responseHeaders);
    let n = keys.length;
    while (n--) {
      const key = keys[n];
      if (key.toLowerCase() === lcHeader) {
        return properties.responseHeaders[key];
      }
    }
    return null;
  }
  
  function normalizeHeaderValue(value) {
    return value.replace(/^[\x09\x0A\x0D\x20]+/, "").replace(/[\x09\x0A\x0D\x20]+$/, "");
  }
  
  function coerceBodyArg(body) {
    // Implements the IDL conversion for `optional (Document or BodyInit)? body = null`
  
    if (body === undefined || body === null) {
      return null;
    }
  
    if (isArrayBuffer(body) || ArrayBuffer.isView(body)) {
      return body;
    }
  
    const impl = idlUtils.implForWrapper(body);
    if (impl) {
      // TODO: allow URLSearchParams or ReadableStream
      if (Blob.isImpl(impl) || FormData.isImpl(impl) || Document.isImpl(impl)) {
        return impl;
      }
    }
  
    return conversions.USVString(body);
  }
  
  function extractBody(bodyInit) {
    // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
    // except we represent the body as a Node.js Buffer instead,
    // or a special case for FormData since we want request to handle that. Probably it would be
    // cleaner (and allow a future without request) if we did the form encoding ourself.
  
    if (Blob.isImpl(bodyInit)) {
      return {
        buffer: bodyInit._buffer,
        contentType: bodyInit.type === "" ? null : bodyInit.type
      };
    } else if (isArrayBuffer(bodyInit)) {
      return {
        buffer: Buffer.from(bodyInit),
        contentType: null
      };
    } else if (ArrayBuffer.isView(bodyInit)) {
      return {
        buffer: Buffer.from(bodyInit.buffer, bodyInit.byteOffset, bodyInit.byteLength),
        contentType: null
      };
    } else if (FormData.isImpl(bodyInit)) {
      const formData = [];
      for (const entry of bodyInit._entries) {
        let val;
        if (Blob.isImpl(entry.value)) {
          const blob = entry.value;
          val = {
            name: entry.name,
            value: blob._buffer,
            options: {
              filename: blob.name,
              contentType: blob.type,
              knownLength: blob.size
            }
          };
        } else {
          val = entry;
        }
  
        formData.push(val);
      }
  
      return { formData };
    }
  
    // Must be a string
    return {
      buffer: Buffer.from(bodyInit, "utf-8"),
      contentType: "text/plain;charset=UTF-8"
    };
  }
  