wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js
changeset 18 be944660c56a
parent 9 177826044cd9
child 19 3d72ae0968f4
--- 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)
     }
   }