[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/dist/vendor/ -> wp-polyfill-formdata.js (source)

   1  /* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
   2  
   3  /* global FormData self Blob File */
   4  /* eslint-disable no-inner-declarations */
   5  
   6  if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
   7    const global = typeof globalThis === 'object'
   8      ? globalThis
   9      : typeof window === 'object'
  10        ? window
  11        : typeof self === 'object' ? self : this
  12  
  13    // keep a reference to native implementation
  14    const _FormData = global.FormData
  15  
  16    // To be monkey patched
  17    const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
  18    const _fetch = global.Request && global.fetch
  19    const _sendBeacon = global.navigator && global.navigator.sendBeacon
  20    // Might be a worker thread...
  21    const _match = global.Element && global.Element.prototype
  22  
  23    // Unable to patch Request/Response constructor correctly #109
  24    // only way is to use ES6 class extend
  25    // https://github.com/babel/babel/issues/1966
  26  
  27    const stringTag = global.Symbol && Symbol.toStringTag
  28  
  29    // Add missing stringTags to blob and files
  30    if (stringTag) {
  31      if (!Blob.prototype[stringTag]) {
  32        Blob.prototype[stringTag] = 'Blob'
  33      }
  34  
  35      if ('File' in global && !File.prototype[stringTag]) {
  36        File.prototype[stringTag] = 'File'
  37      }
  38    }
  39  
  40    // Fix so you can construct your own File
  41    try {
  42      new File([], '') // eslint-disable-line
  43    } catch (a) {
  44      global.File = function File (b, d, c) {
  45        const blob = new Blob(b, c || {})
  46        const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
  47  
  48        Object.defineProperties(blob, {
  49          name: {
  50            value: d
  51          },
  52          lastModified: {
  53            value: +t
  54          },
  55          toString: {
  56            value () {
  57              return '[object File]'
  58            }
  59          }
  60        })
  61  
  62        if (stringTag) {
  63          Object.defineProperty(blob, stringTag, {
  64            value: 'File'
  65          })
  66        }
  67  
  68        return blob
  69      }
  70    }
  71  
  72    function ensureArgs (args, expected) {
  73      if (args.length < expected) {
  74        throw new TypeError(`$expected} argument required, but only $args.length} present.`)
  75      }
  76    }
  77  
  78    /**
  79     * @param {string} name
  80     * @param {string | undefined} filename
  81     * @returns {[string, File|string]}
  82     */
  83    function normalizeArgs (name, value, filename) {
  84      if (value instanceof Blob) {
  85        filename = filename !== undefined
  86        ? String(filename + '')
  87        : typeof value.name === 'string'
  88        ? value.name
  89        : 'blob'
  90  
  91        if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {
  92          value = new File([value], filename)
  93        }
  94        return [String(name), value]
  95      }
  96      return [String(name), String(value)]
  97    }
  98  
  99    // normalize line feeds for textarea
 100    // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
 101    function normalizeLinefeeds (value) {
 102      return value.replace(/\r?\n|\r/g, '\r\n')
 103    }
 104  
 105    /**
 106     * @template T
 107     * @param {ArrayLike<T>} arr
 108     * @param {{ (elm: T): void; }} cb
 109     */
 110    function each (arr, cb) {
 111      for (let i = 0; i < arr.length; i++) {
 112        cb(arr[i])
 113      }
 114    }
 115  
 116    const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
 117  
 118    /**
 119     * @implements {Iterable}
 120     */
 121    class FormDataPolyfill {
 122      /**
 123       * FormData class
 124       *
 125       * @param {HTMLFormElement=} form
 126       */
 127      constructor (form) {
 128        /** @type {[string, string|File][]} */
 129        this._data = []
 130  
 131        const self = this
 132        form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {
 133          if (
 134            !elm.name ||
 135            elm.disabled ||
 136            elm.type === 'submit' ||
 137            elm.type === 'button' ||
 138            elm.matches('form fieldset[disabled] *')
 139          ) return
 140  
 141          if (elm.type === 'file') {
 142            const files = elm.files && elm.files.length
 143              ? elm.files
 144              : [new File([], '', { type: 'application/octet-stream' })] // #78
 145  
 146            each(files, file => {
 147              self.append(elm.name, file)
 148            })
 149          } else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
 150            each(elm.options, opt => {
 151              !opt.disabled && opt.selected && self.append(elm.name, opt.value)
 152            })
 153          } else if (elm.type === 'checkbox' || elm.type === 'radio') {
 154            if (elm.checked) self.append(elm.name, elm.value)
 155          } else {
 156            const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value
 157            self.append(elm.name, value)
 158          }
 159        })
 160      }
 161  
 162      /**
 163       * Append a field
 164       *
 165       * @param   {string}           name      field name
 166       * @param   {string|Blob|File} value     string / blob / file
 167       * @param   {string=}          filename  filename to use with blob
 168       * @return  {undefined}
 169       */
 170      append (name, value, filename) {
 171        ensureArgs(arguments, 2)
 172        this._data.push(normalizeArgs(name, value, filename))
 173      }
 174  
 175      /**
 176       * Delete all fields values given name
 177       *
 178       * @param   {string}  name  Field name
 179       * @return  {undefined}
 180       */
 181      delete (name) {
 182        ensureArgs(arguments, 1)
 183        const result = []
 184        name = String(name)
 185  
 186        each(this._data, entry => {
 187          entry[0] !== name && result.push(entry)
 188        })
 189  
 190        this._data = result
 191      }
 192  
 193      /**
 194       * Iterate over all fields as [name, value]
 195       *
 196       * @return {Iterator}
 197       */
 198      * entries () {
 199        for (var i = 0; i < this._data.length; i++) {
 200          yield this._data[i]
 201        }
 202      }
 203  
 204      /**
 205       * Iterate over all fields
 206       *
 207       * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
 208       * @param   {Object=}   thisArg   `this` context for callback function
 209       */
 210      forEach (callback, thisArg) {
 211        ensureArgs(arguments, 1)
 212        for (const [name, value] of this) {
 213          callback.call(thisArg, value, name, this)
 214        }
 215      }
 216  
 217      /**
 218       * Return first field value given name
 219       * or null if non existent
 220       *
 221       * @param   {string}  name      Field name
 222       * @return  {string|File|null}  value Fields value
 223       */
 224      get (name) {
 225        ensureArgs(arguments, 1)
 226        const entries = this._data
 227        name = String(name)
 228        for (let i = 0; i < entries.length; i++) {
 229          if (entries[i][0] === name) {
 230            return entries[i][1]
 231          }
 232        }
 233        return null
 234      }
 235  
 236      /**
 237       * Return all fields values given name
 238       *
 239       * @param   {string}  name  Fields name
 240       * @return  {Array}         [{String|File}]
 241       */
 242      getAll (name) {
 243        ensureArgs(arguments, 1)
 244        const result = []
 245        name = String(name)
 246        each(this._data, data => {
 247          data[0] === name && result.push(data[1])
 248        })
 249  
 250        return result
 251      }
 252  
 253      /**
 254       * Check for field name existence
 255       *
 256       * @param   {string}   name  Field name
 257       * @return  {boolean}
 258       */
 259      has (name) {
 260        ensureArgs(arguments, 1)
 261        name = String(name)
 262        for (let i = 0; i < this._data.length; i++) {
 263          if (this._data[i][0] === name) {
 264            return true
 265          }
 266        }
 267        return false
 268      }
 269  
 270      /**
 271       * Iterate over all fields name
 272       *
 273       * @return {Iterator}
 274       */
 275      * keys () {
 276        for (const [name] of this) {
 277          yield name
 278        }
 279      }
 280  
 281      /**
 282       * Overwrite all values given name
 283       *
 284       * @param   {string}    name      Filed name
 285       * @param   {string}    value     Field value
 286       * @param   {string=}   filename  Filename (optional)
 287       */
 288      set (name, value, filename) {
 289        ensureArgs(arguments, 2)
 290        name = String(name)
 291        /** @type {[string, string|File][]} */
 292        const result = []
 293        const args = normalizeArgs(name, value, filename)
 294        let replace = true
 295  
 296        // - replace the first occurrence with same name
 297        // - discards the remaining with same name
 298        // - while keeping the same order items where added
 299        each(this._data, data => {
 300          data[0] === name
 301            ? replace && (replace = !result.push(args))
 302            : result.push(data)
 303        })
 304  
 305        replace && result.push(args)
 306  
 307        this._data = result
 308      }
 309  
 310      /**
 311       * Iterate over all fields
 312       *
 313       * @return {Iterator}
 314       */
 315      * values () {
 316        for (const [, value] of this) {
 317          yield value
 318        }
 319      }
 320  
 321      /**
 322       * Return a native (perhaps degraded) FormData with only a `append` method
 323       * Can throw if it's not supported
 324       *
 325       * @return {FormData}
 326       */
 327      ['_asNative'] () {
 328        const fd = new _FormData()
 329  
 330        for (const [name, value] of this) {
 331          fd.append(name, value)
 332        }
 333  
 334        return fd
 335      }
 336  
 337      /**
 338       * [_blob description]
 339       *
 340       * @return {Blob} [description]
 341       */
 342      ['_blob'] () {
 343          const boundary = '----formdata-polyfill-' + Math.random(),
 344            chunks = [],
 345            p = `--$boundary}\r\nContent-Disposition: form-data; name="`
 346          this.forEach((value, name) => typeof value == 'string'
 347            ? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n$normalizeLinefeeds(value)}\r\n`)
 348            : 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`))
 349          chunks.push(`--$boundary}--`)
 350          return new Blob(chunks, {
 351            type: "multipart/form-data; boundary=" + boundary
 352          })
 353      }
 354  
 355      /**
 356       * The class itself is iterable
 357       * alias for formdata.entries()
 358       *
 359       * @return {Iterator}
 360       */
 361      [Symbol.iterator] () {
 362        return this.entries()
 363      }
 364  
 365      /**
 366       * Create the default string description.
 367       *
 368       * @return  {string} [object FormData]
 369       */
 370      toString () {
 371        return '[object FormData]'
 372      }
 373    }
 374  
 375    if (_match && !_match.matches) {
 376      _match.matches =
 377        _match.matchesSelector ||
 378        _match.mozMatchesSelector ||
 379        _match.msMatchesSelector ||
 380        _match.oMatchesSelector ||
 381        _match.webkitMatchesSelector ||
 382        function (s) {
 383          var matches = (this.document || this.ownerDocument).querySelectorAll(s)
 384          var i = matches.length
 385          while (--i >= 0 && matches.item(i) !== this) {}
 386          return i > -1
 387        }
 388    }
 389  
 390    if (stringTag) {
 391      /**
 392       * Create the default string description.
 393       * It is accessed internally by the Object.prototype.toString().
 394       */
 395      FormDataPolyfill.prototype[stringTag] = 'FormData'
 396    }
 397  
 398    // Patch xhr's send method to call _blob transparently
 399    if (_send) {
 400      const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader
 401  
 402      global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
 403        setRequestHeader.call(this, name, value)
 404        if (name.toLowerCase() === 'content-type') this._hasContentType = true
 405      }
 406  
 407      global.XMLHttpRequest.prototype.send = function (data) {
 408        // need to patch send b/c old IE don't send blob's type (#44)
 409        if (data instanceof FormDataPolyfill) {
 410          const blob = data['_blob']()
 411          if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)
 412          _send.call(this, blob)
 413        } else {
 414          _send.call(this, data)
 415        }
 416      }
 417    }
 418  
 419    // Patch fetch's function to call _blob transparently
 420    if (_fetch) {
 421      global.fetch = function (input, init) {
 422        if (init && init.body && init.body instanceof FormDataPolyfill) {
 423          init.body = init.body['_blob']()
 424        }
 425  
 426        return _fetch.call(this, input, init)
 427      }
 428    }
 429  
 430    // Patch navigator.sendBeacon to use native FormData
 431    if (_sendBeacon) {
 432      global.navigator.sendBeacon = function (url, data) {
 433        if (data instanceof FormDataPolyfill) {
 434          data = data['_asNative']()
 435        }
 436        return _sendBeacon.call(this, url, data)
 437      }
 438    }
 439  
 440    global['FormData'] = FormDataPolyfill
 441  }


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref