diff -r 34716fd837a4 -r be944660c56a wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js --- a/wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js Tue Dec 15 15:52:01 2020 +0100 +++ b/wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js Wed Sep 21 18:19:35 2022 +0200 @@ -1,7 +1,12 @@ -if (typeof FormData === 'undefined' || !FormData.prototype.keys) { - const global = typeof window === 'object' - ? window : typeof self === 'object' - ? self : this +/* 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' + ? globalThis + : typeof window === 'object' + ? window + : typeof self === 'object' ? self : this // keep a reference to native implementation const _FormData = global.FormData @@ -9,16 +14,15 @@ // To be monkey patched 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 constructor correctly - // const _Request = global.Request + // 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 - const map = new WeakMap - const wm = o => map.get(o) - const arrayFrom = Array.from || (obj => [].slice.call(obj)) // Add missing stringTags to blob and files if (stringTag) { @@ -33,11 +37,11 @@ // Fix so you can construct your own File try { - new File([], '') + new File([], '') // eslint-disable-line } catch (a) { - global.File = function(b, d, c) { + 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 + const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date() Object.defineProperties(blob, { name: { @@ -50,7 +54,7 @@ value: +t }, toString: { - value() { + value () { return '[object File]' } } @@ -66,41 +70,42 @@ } } - function normalizeValue([value, filename]) { - if (value instanceof Blob) + function normalizeValue ([name, value, filename]) { + if (value instanceof Blob) { // Should always returns a new File instance // console.assert(fd.get(x) !== fd.get(x)) value = new File([value], filename, { type: value.type, lastModified: value.lastModified }) + } - return value + return [name, value] } - function stringify(name) { - if (!arguments.length) - throw new TypeError('1 argument required, but only 0 present.') - - return [name + ''] + function ensureArgs (args, expected) { + if (args.length < expected) { + throw new TypeError(`${expected} argument required, but only ${args.length} present.`) + } } - function normalizeArgs(name, value, filename) { - if (arguments.length < 2) - throw new TypeError( - `2 arguments required, but only ${arguments.length} present.` - ) - + function normalizeArgs (name, value, filename) { return value instanceof Blob // normalize name and filename if adding an attachment - ? [name + '', value, filename !== undefined + ? [String(name), value, filename !== undefined ? filename + '' // Cast filename to string if 3th arg isn't undefined : typeof value.name === 'string' // if name prop exist ? value.name // Use File.name : 'blob'] // otherwise fallback to Blob // If no attachment, just cast the args to strings - : [name + '', value + ''] + : [String(name), String(value)] + } + + // normalize linefeeds for textareas + // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation + function normalizeLinefeeds (value) { + return value.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n') } function each (arr, cb) { @@ -113,25 +118,31 @@ * @implements {Iterable} */ class FormDataPolyfill { - /** * FormData class * * @param {HTMLElement=} form */ - constructor(form) { - map.set(this, Object.create(null)) - - if (!form) - return this + constructor (form) { + this._data = [] const self = this - each(form.elements, elm => { - if (!elm.name || elm.disabled || elm.type === 'submit' || elm.type === 'button') return + form && each(form.elements, elm => { + if ( + !elm.name || + elm.disabled || + elm.type === 'submit' || + elm.type === 'button' || + elm.matches('form fieldset[disabled] *') + ) return if (elm.type === 'file') { - each(elm.files || [], file => { + const files = elm.files && elm.files.length + ? elm.files + : [new File([], '', { type: 'application/octet-stream' })] // #78 + + each(files, file => { self.append(elm.name, file) }) } else if (elm.type === 'select-multiple' || elm.type === 'select-one') { @@ -141,52 +152,52 @@ } else if (elm.type === 'checkbox' || elm.type === 'radio') { if (elm.checked) self.append(elm.name, elm.value) } else { - self.append(elm.name, elm.value) + const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value + self.append(elm.name, value) } }) } - /** * Append a field * - * @param {String} name field name - * @param {String|Blob|File} value string / blob / file - * @param {String=} filename filename to use with blob - * @return {Undefined} + * @param {string} name field name + * @param {string|Blob|File} value string / blob / file + * @param {string=} filename filename to use with blob + * @return {undefined} */ - append(name, value, filename) { - const map = wm(this) - - if (!map[name]) - map[name] = [] - - map[name].push([value, filename]) + append (name, value, filename) { + ensureArgs(arguments, 2) + this._data.push(normalizeArgs(name, value, filename)) } - /** * Delete all fields values given name * - * @param {String} name Field name - * @return {Undefined} + * @param {string} name Field name + * @return {undefined} */ - delete(name) { - delete wm(this)[name] + delete (name) { + ensureArgs(arguments, 1) + const result = [] + name = String(name) + + each(this._data, entry => { + entry[0] !== name && result.push(entry) + }) + + this._data = result } - /** * Iterate over all fields as [name, value] * * @return {Iterator} */ - *entries() { - const map = wm(this) - - for (let name in map) - for (let value of map[name]) - yield [name, normalizeValue(value)] + * entries () { + for (var i = 0; i < this._data.length; i++) { + yield normalizeValue(this._data[i]) + } } /** @@ -194,115 +205,150 @@ * * @param {Function} callback Executed for each item with parameters (value, name, thisArg) * @param {Object=} thisArg `this` context for callback function - * @return {Undefined} + * @return {undefined} */ - forEach(callback, thisArg) { - for (let [name, value] of this) + forEach (callback, thisArg) { + ensureArgs(arguments, 1) + for (const [name, value] of this) { callback.call(thisArg, value, name, this) + } } - /** * Return first field value given name * or null if non existen * - * @param {String} name Field name - * @return {String|File|null} value Fields value + * @param {string} name Field name + * @return {string|File|null} value Fields value */ - get(name) { - const map = wm(this) - return map[name] ? normalizeValue(map[name][0]) : null + get (name) { + ensureArgs(arguments, 1) + const entries = this._data + name = String(name) + for (let i = 0; i < entries.length; i++) { + if (entries[i][0] === name) { + return normalizeValue(entries[i])[1] + } + } + return null } - /** * Return all fields values given name * - * @param {String} name Fields name + * @param {string} name Fields name * @return {Array} [{String|File}] */ - getAll(name) { - return (wm(this)[name] || []).map(normalizeValue) + getAll (name) { + ensureArgs(arguments, 1) + const result = [] + name = String(name) + each(this._data, data => { + data[0] === name && result.push(normalizeValue(data)[1]) + }) + + return result } - /** * Check for field name existence * - * @param {String} name Field name + * @param {string} name Field name * @return {boolean} */ - has(name) { - return name in wm(this) + has (name) { + ensureArgs(arguments, 1) + name = String(name) + for (let i = 0; i < this._data.length; i++) { + if (this._data[i][0] === name) { + return true + } + } + return false } - /** * Iterate over all fields name * * @return {Iterator} */ - *keys() { - for (let [name] of this) + * keys () { + for (const [name] of this) { yield name + } } - /** * Overwrite all values given name * - * @param {String} name Filed name - * @param {String} value Field value - * @param {String=} filename Filename (optional) - * @return {Undefined} + * @param {string} name Filed name + * @param {string} value Field value + * @param {string=} filename Filename (optional) + * @return {undefined} */ - set(name, value, filename) { - wm(this)[name] = [[value, filename]] + set (name, value, filename) { + ensureArgs(arguments, 2) + name = String(name) + const result = [] + const args = normalizeArgs(name, value, filename) + let replace = true + + // - replace the first occurrence with same name + // - discards the remaning with same name + // - while keeping the same order items where added + each(this._data, data => { + data[0] === name + ? replace && (replace = !result.push(args)) + : result.push(data) + }) + + replace && result.push(args) + + this._data = result } - /** * Iterate over all fields * * @return {Iterator} */ - *values() { - for (let [name, value] of this) + * values () { + for (const [, value] of this) { yield value + } } - /** * Return a native (perhaps degraded) FormData with only a `append` method * Can throw if it's not supported * * @return {FormData} */ - ['_asNative']() { - const fd = new _FormData + ['_asNative'] () { + const fd = new _FormData() - for (let [name, value] of this) + for (const [name, value] of this) { fd.append(name, value) + } return fd } - /** * [_blob description] * * @return {Blob} [description] */ - ['_blob']() { + ['_blob'] () { const boundary = '----formdata-polyfill-' + Math.random() const chunks = [] - for (let [name, value] of this) { + for (const [name, value] of this) { chunks.push(`--${boundary}\r\n`) if (value instanceof Blob) { chunks.push( - `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`, + `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n` + `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`, value, '\r\n' @@ -316,67 +362,68 @@ chunks.push(`--${boundary}--`) - return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary}) + return new Blob(chunks, { + type: 'multipart/form-data; boundary=' + boundary + }) } - /** * The class itself is iterable * alias for formdata.entries() * * @return {Iterator} */ - [Symbol.iterator]() { + [Symbol.iterator] () { return this.entries() } - /** * Create the default string description. * - * @return {String} [object FormData] + * @return {string} [object FormData] */ - toString() { + toString () { return '[object FormData]' } } + if (_match && !_match.matches) { + _match.matches = + _match.matchesSelector || + _match.mozMatchesSelector || + _match.msMatchesSelector || + _match.oMatchesSelector || + _match.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s) + var i = matches.length + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + } + } if (stringTag) { /** * Create the default string description. * It is accessed internally by the Object.prototype.toString(). - * - * @return {String} FormData */ FormDataPolyfill.prototype[stringTag] = 'FormData' } - const decorations = [ - ['append', normalizeArgs], - ['delete', stringify], - ['get', stringify], - ['getAll', stringify], - ['has', stringify], - ['set', normalizeArgs] - ] - - decorations.forEach(arr => { - const orig = FormDataPolyfill.prototype[arr[0]] - FormDataPolyfill.prototype[arr[0]] = function() { - return orig.apply(this, arr[1].apply(this, arrayFrom(arguments))) - } - }) - // Patch xhr's send method to call _blob transparently if (_send) { - XMLHttpRequest.prototype.send = function(data) { - // I would check if Content-Type isn't already set - // But xhr lacks getRequestHeaders functionallity - // https://github.com/jimmywarting/FormData/issues/44 + 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']() - this.setRequestHeader('Content-Type', blob.type) + if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type) _send.call(this, blob) } else { _send.call(this, data) @@ -386,14 +433,22 @@ // Patch fetch's function to call _blob transparently if (_fetch) { - const _fetch = global.fetch - - global.fetch = function(input, init) { + global.fetch = function (input, init) { if (init && init.body && init.body instanceof FormDataPolyfill) { init.body = init.body['_blob']() } - return _fetch(input, init) + return _fetch.call(this, input, init) + } + } + + // Patch navigator.sendBeacon to use native FormData + if (_sendBeacon) { + global.navigator.sendBeacon = function (url, data) { + if (data instanceof FormDataPolyfill) { + data = data['_asNative']() + } + return _sendBeacon.call(this, url, data) } }