: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
/* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
/* global FormData self Blob File */
/* eslint-disable no-inner-declarations */
if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
const global = typeof globalThis === 'object'
: typeof window === 'object'
: typeof self === 'object' ? self : this
// keep a reference to native implementation
const _FormData = global.FormData
const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
const _fetch = global.Request && global.fetch
const _sendBeacon = global.navigator && global.navigator.sendBeacon
// Might be a worker thread...
const _match = global.Element && global.Element.prototype
// Unable to patch Request/Response constructor correctly #109
// only way is to use ES6 class extend
// https://github.com/babel/babel/issues/1966
const stringTag = global.Symbol && Symbol.toStringTag
// Add missing stringTags to blob and files
if (!Blob.prototype[stringTag]) {
Blob.prototype[stringTag] = 'Blob'
if ('File' in global && !File.prototype[stringTag]) {
File.prototype[stringTag] = 'File'
// Fix so you can construct your own File
new File([], '') // eslint-disable-line
global.File = function File (b, d, c) {
const blob = new Blob(b, c || {})
const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
Object.defineProperties(blob, {
Object.defineProperty(blob, stringTag, {
function ensureArgs (args, expected) {
if (args.length < expected) {
throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
* @param {string | undefined} filename
* @returns {[string, File|string]}
function normalizeArgs (name, value, filename) {
if (value instanceof Blob) {
filename = filename !== undefined
: typeof value.name === 'string'
if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {
value = new File([value], filename)
return [String(name), value]
return [String(name), String(value)]
// normalize line feeds for textarea
// https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
function normalizeLinefeeds (value) {
return value.replace(/\r?\n|\r/g, '\r\n')
* @param {ArrayLike<T>} arr
* @param {{ (elm: T): void; }} cb
function each (arr, cb) {
for (let i = 0; i < arr.length; i++) {
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
* @param {HTMLFormElement=} form
/** @type {[string, string|File][]} */
form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {
elm.matches('form fieldset[disabled] *')
if (elm.type === 'file') {
const files = elm.files && elm.files.length
: [new File([], '', { type: 'application/octet-stream' })] // #78
self.append(elm.name, file)
} else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
each(elm.options, opt => {
!opt.disabled && opt.selected && self.append(elm.name, opt.value)
} else if (elm.type === 'checkbox' || elm.type === 'radio') {
if (elm.checked) self.append(elm.name, elm.value)
const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value
self.append(elm.name, value)
* @param {string} name field name
* @param {string|Blob|File} value string / blob / file
* @param {string=} filename filename to use with blob
append (name, value, filename) {
this._data.push(normalizeArgs(name, value, filename))
* Delete all fields values given name
* @param {string} name Field name
each(this._data, entry => {
entry[0] !== name && result.push(entry)
* Iterate over all fields as [name, value]
for (var i = 0; i < this._data.length; i++) {
* Iterate over all fields
* @param {Function} callback Executed for each item with parameters (value, name, thisArg)
* @param {Object=} thisArg `this` context for callback function
forEach (callback, thisArg) {
for (const [name, value] of this) {
callback.call(thisArg, value, name, this)
* Return first field value given name
* or null if non existent
* @param {string} name Field name
* @return {string|File|null} value Fields value
const entries = this._data
for (let i = 0; i < entries.length; i++) {
if (entries[i][0] === name) {
* Return all fields values given name
* @param {string} name Fields name
* @return {Array} [{String|File}]
each(this._data, data => {
data[0] === name && result.push(data[1])
* Check for field name existence
* @param {string} name Field name
for (let i = 0; i < this._data.length; i++) {
if (this._data[i][0] === name) {
* Iterate over all fields name
for (const [name] of this) {
* Overwrite all values given name
* @param {string} name Filed name
* @param {string} value Field value
* @param {string=} filename Filename (optional)
set (name, value, filename) {
/** @type {[string, string|File][]} */
const args = normalizeArgs(name, value, filename)
// - replace the first occurrence with same name
// - discards the remaining with same name
// - while keeping the same order items where added
each(this._data, data => {
? replace && (replace = !result.push(args))
replace && result.push(args)
* Iterate over all fields
for (const [, value] of this) {
* Return a native (perhaps degraded) FormData with only a `append` method
* Can throw if it's not supported
const fd = new _FormData()
for (const [name, value] of this) {
* @return {Blob} [description]
const boundary = '----formdata-polyfill-' + Math.random(),
p = `--${boundary}\r\nContent-Disposition: form-data; name="`
this.forEach((value, name) => typeof value == 'string'
? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
: chunks.push(p + escape(normalizeLinefeeds(name)) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))
chunks.push(`--${boundary}--`)
return new Blob(chunks, {
type: "multipart/form-data; boundary=" + boundary
* The class itself is iterable
* alias for formdata.entries()
* Create the default string description.
* @return {string} [object FormData]
return '[object FormData]'
if (_match && !_match.matches) {
_match.matchesSelector ||
_match.mozMatchesSelector ||
_match.msMatchesSelector ||
_match.oMatchesSelector ||
_match.webkitMatchesSelector ||
var matches = (this.document || this.ownerDocument).querySelectorAll(s)
while (--i >= 0 && matches.item(i) !== this) {}
* Create the default string description.
* It is accessed internally by the Object.prototype.toString().
FormDataPolyfill.prototype[stringTag] = 'FormData'
// Patch xhr's send method to call _blob transparently
const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader
global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
setRequestHeader.call(this, name, value)
if (name.toLowerCase() === 'content-type') this._hasContentType = true
global.XMLHttpRequest.prototype.send = function (data) {
// need to patch send b/c old IE don't send blob's type (#44)
if (data instanceof FormDataPolyfill) {
const blob = data['_blob']()
if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)
// Patch fetch's function to call _blob transparently
global.fetch = function (input, init) {
if (init && init.body && init.body instanceof FormDataPolyfill) {
init.body = init.body['_blob']()
return _fetch.call(this, input, init)
// Patch navigator.sendBeacon to use native FormData
global.navigator.sendBeacon = function (url, data) {
if (data instanceof FormDataPolyfill) {
data = data['_asNative']()
return _sendBeacon.call(this, url, data)
global['FormData'] = FormDataPolyfill