wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js
changeset 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     1 if (typeof FormData === 'undefined' || !FormData.prototype.keys) {
       
     2   const global = typeof window === 'object'
       
     3     ? window : typeof self === 'object'
       
     4     ? self : this
       
     5 
       
     6   // keep a reference to native implementation
       
     7   const _FormData = global.FormData
       
     8 
       
     9   // To be monkey patched
       
    10   const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
       
    11   const _fetch = global.Request && global.fetch
       
    12 
       
    13   // Unable to patch Request constructor correctly
       
    14   // const _Request = global.Request
       
    15   // only way is to use ES6 class extend
       
    16   // https://github.com/babel/babel/issues/1966
       
    17 
       
    18   const stringTag = global.Symbol && Symbol.toStringTag
       
    19   const map = new WeakMap
       
    20   const wm = o => map.get(o)
       
    21   const arrayFrom = Array.from || (obj => [].slice.call(obj))
       
    22 
       
    23   // Add missing stringTags to blob and files
       
    24   if (stringTag) {
       
    25     if (!Blob.prototype[stringTag]) {
       
    26       Blob.prototype[stringTag] = 'Blob'
       
    27     }
       
    28 
       
    29     if ('File' in global && !File.prototype[stringTag]) {
       
    30       File.prototype[stringTag] = 'File'
       
    31     }
       
    32   }
       
    33 
       
    34   // Fix so you can construct your own File
       
    35   try {
       
    36     new File([], '')
       
    37   } catch (a) {
       
    38     global.File = function(b, d, c) {
       
    39       const blob = new Blob(b, c)
       
    40       const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date
       
    41 
       
    42       Object.defineProperties(blob, {
       
    43         name: {
       
    44           value: d
       
    45         },
       
    46         lastModifiedDate: {
       
    47           value: t
       
    48         },
       
    49         lastModified: {
       
    50           value: +t
       
    51         },
       
    52         toString: {
       
    53           value() {
       
    54             return '[object File]'
       
    55           }
       
    56         }
       
    57       })
       
    58 
       
    59       if (stringTag) {
       
    60         Object.defineProperty(blob, stringTag, {
       
    61           value: 'File'
       
    62         })
       
    63       }
       
    64 
       
    65       return blob
       
    66     }
       
    67   }
       
    68 
       
    69   function normalizeValue([value, filename]) {
       
    70     if (value instanceof Blob)
       
    71       // Should always returns a new File instance
       
    72       // console.assert(fd.get(x) !== fd.get(x))
       
    73       value = new File([value], filename, {
       
    74         type: value.type,
       
    75         lastModified: value.lastModified
       
    76       })
       
    77 
       
    78     return value
       
    79   }
       
    80 
       
    81   function stringify(name) {
       
    82     if (!arguments.length)
       
    83       throw new TypeError('1 argument required, but only 0 present.')
       
    84 
       
    85     return [name + '']
       
    86   }
       
    87 
       
    88   function normalizeArgs(name, value, filename) {
       
    89     if (arguments.length < 2)
       
    90       throw new TypeError(
       
    91         `2 arguments required, but only ${arguments.length} present.`
       
    92       )
       
    93 
       
    94     return value instanceof Blob
       
    95       // normalize name and filename if adding an attachment
       
    96       ? [name + '', value, filename !== undefined
       
    97         ? filename + '' // Cast filename to string if 3th arg isn't undefined
       
    98         : typeof value.name === 'string' // if name prop exist
       
    99           ? value.name // Use File.name
       
   100           : 'blob'] // otherwise fallback to Blob
       
   101 
       
   102       // If no attachment, just cast the args to strings
       
   103       : [name + '', value + '']
       
   104   }
       
   105 
       
   106   function each (arr, cb) {
       
   107     for (let i = 0; i < arr.length; i++) {
       
   108       cb(arr[i])
       
   109     }
       
   110   }
       
   111 
       
   112   /**
       
   113    * @implements {Iterable}
       
   114    */
       
   115   class FormDataPolyfill {
       
   116 
       
   117     /**
       
   118      * FormData class
       
   119      *
       
   120      * @param {HTMLElement=} form
       
   121      */
       
   122     constructor(form) {
       
   123       map.set(this, Object.create(null))
       
   124 
       
   125       if (!form)
       
   126         return this
       
   127 
       
   128       const self = this
       
   129 
       
   130       each(form.elements, elm => {
       
   131         if (!elm.name || elm.disabled || elm.type === 'submit' || elm.type === 'button') return
       
   132 
       
   133         if (elm.type === 'file') {
       
   134           each(elm.files || [], file => {
       
   135             self.append(elm.name, file)
       
   136           })
       
   137         } else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
       
   138           each(elm.options, opt => {
       
   139             !opt.disabled && opt.selected && self.append(elm.name, opt.value)
       
   140           })
       
   141         } else if (elm.type === 'checkbox' || elm.type === 'radio') {
       
   142           if (elm.checked) self.append(elm.name, elm.value)
       
   143         } else {
       
   144           self.append(elm.name, elm.value)
       
   145         }
       
   146       })
       
   147     }
       
   148 
       
   149 
       
   150     /**
       
   151      * Append a field
       
   152      *
       
   153      * @param   {String}           name      field name
       
   154      * @param   {String|Blob|File} value     string / blob / file
       
   155      * @param   {String=}          filename  filename to use with blob
       
   156      * @return  {Undefined}
       
   157      */
       
   158     append(name, value, filename) {
       
   159       const map = wm(this)
       
   160 
       
   161       if (!map[name])
       
   162         map[name] = []
       
   163 
       
   164       map[name].push([value, filename])
       
   165     }
       
   166 
       
   167 
       
   168     /**
       
   169      * Delete all fields values given name
       
   170      *
       
   171      * @param   {String}  name  Field name
       
   172      * @return  {Undefined}
       
   173      */
       
   174     delete(name) {
       
   175       delete wm(this)[name]
       
   176     }
       
   177 
       
   178 
       
   179     /**
       
   180      * Iterate over all fields as [name, value]
       
   181      *
       
   182      * @return {Iterator}
       
   183      */
       
   184     *entries() {
       
   185       const map = wm(this)
       
   186 
       
   187       for (let name in map)
       
   188         for (let value of map[name])
       
   189           yield [name, normalizeValue(value)]
       
   190     }
       
   191 
       
   192     /**
       
   193      * Iterate over all fields
       
   194      *
       
   195      * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
       
   196      * @param   {Object=}   thisArg   `this` context for callback function
       
   197      * @return  {Undefined}
       
   198      */
       
   199     forEach(callback, thisArg) {
       
   200       for (let [name, value] of this)
       
   201         callback.call(thisArg, value, name, this)
       
   202     }
       
   203 
       
   204 
       
   205     /**
       
   206      * Return first field value given name
       
   207      * or null if non existen
       
   208      *
       
   209      * @param   {String}  name      Field name
       
   210      * @return  {String|File|null}  value Fields value
       
   211      */
       
   212     get(name) {
       
   213       const map = wm(this)
       
   214       return map[name] ? normalizeValue(map[name][0]) : null
       
   215     }
       
   216 
       
   217 
       
   218     /**
       
   219      * Return all fields values given name
       
   220      *
       
   221      * @param   {String}  name  Fields name
       
   222      * @return  {Array}         [{String|File}]
       
   223      */
       
   224     getAll(name) {
       
   225       return (wm(this)[name] || []).map(normalizeValue)
       
   226     }
       
   227 
       
   228 
       
   229     /**
       
   230      * Check for field name existence
       
   231      *
       
   232      * @param   {String}   name  Field name
       
   233      * @return  {boolean}
       
   234      */
       
   235     has(name) {
       
   236       return name in wm(this)
       
   237     }
       
   238 
       
   239 
       
   240     /**
       
   241      * Iterate over all fields name
       
   242      *
       
   243      * @return {Iterator}
       
   244      */
       
   245     *keys() {
       
   246       for (let [name] of this)
       
   247         yield name
       
   248     }
       
   249 
       
   250 
       
   251     /**
       
   252      * Overwrite all values given name
       
   253      *
       
   254      * @param   {String}    name      Filed name
       
   255      * @param   {String}    value     Field value
       
   256      * @param   {String=}   filename  Filename (optional)
       
   257      * @return  {Undefined}
       
   258      */
       
   259     set(name, value, filename) {
       
   260       wm(this)[name] = [[value, filename]]
       
   261     }
       
   262 
       
   263 
       
   264     /**
       
   265      * Iterate over all fields
       
   266      *
       
   267      * @return {Iterator}
       
   268      */
       
   269     *values() {
       
   270       for (let [name, value] of this)
       
   271         yield value
       
   272     }
       
   273 
       
   274 
       
   275     /**
       
   276      * Return a native (perhaps degraded) FormData with only a `append` method
       
   277      * Can throw if it's not supported
       
   278      *
       
   279      * @return {FormData}
       
   280      */
       
   281     ['_asNative']() {
       
   282       const fd = new _FormData
       
   283 
       
   284       for (let [name, value] of this)
       
   285         fd.append(name, value)
       
   286 
       
   287       return fd
       
   288     }
       
   289 
       
   290 
       
   291     /**
       
   292      * [_blob description]
       
   293      *
       
   294      * @return {Blob} [description]
       
   295      */
       
   296     ['_blob']() {
       
   297       const boundary = '----formdata-polyfill-' + Math.random()
       
   298       const chunks = []
       
   299 
       
   300       for (let [name, value] of this) {
       
   301         chunks.push(`--${boundary}\r\n`)
       
   302 
       
   303         if (value instanceof Blob) {
       
   304           chunks.push(
       
   305             `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
       
   306             `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
       
   307             value,
       
   308             '\r\n'
       
   309           )
       
   310         } else {
       
   311           chunks.push(
       
   312             `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
       
   313           )
       
   314         }
       
   315       }
       
   316 
       
   317       chunks.push(`--${boundary}--`)
       
   318 
       
   319       return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
       
   320     }
       
   321 
       
   322 
       
   323     /**
       
   324      * The class itself is iterable
       
   325      * alias for formdata.entries()
       
   326      *
       
   327      * @return  {Iterator}
       
   328      */
       
   329     [Symbol.iterator]() {
       
   330       return this.entries()
       
   331     }
       
   332 
       
   333 
       
   334     /**
       
   335      * Create the default string description.
       
   336      *
       
   337      * @return  {String} [object FormData]
       
   338      */
       
   339     toString() {
       
   340       return '[object FormData]'
       
   341     }
       
   342   }
       
   343 
       
   344 
       
   345   if (stringTag) {
       
   346     /**
       
   347      * Create the default string description.
       
   348      * It is accessed internally by the Object.prototype.toString().
       
   349      *
       
   350      * @return {String} FormData
       
   351      */
       
   352     FormDataPolyfill.prototype[stringTag] = 'FormData'
       
   353   }
       
   354 
       
   355   const decorations = [
       
   356     ['append', normalizeArgs],
       
   357     ['delete', stringify],
       
   358     ['get',    stringify],
       
   359     ['getAll', stringify],
       
   360     ['has',    stringify],
       
   361     ['set',    normalizeArgs]
       
   362   ]
       
   363 
       
   364   decorations.forEach(arr => {
       
   365     const orig = FormDataPolyfill.prototype[arr[0]]
       
   366     FormDataPolyfill.prototype[arr[0]] = function() {
       
   367       return orig.apply(this, arr[1].apply(this, arrayFrom(arguments)))
       
   368     }
       
   369   })
       
   370 
       
   371   // Patch xhr's send method to call _blob transparently
       
   372   if (_send) {
       
   373     XMLHttpRequest.prototype.send = function(data) {
       
   374       // I would check if Content-Type isn't already set
       
   375       // But xhr lacks getRequestHeaders functionallity
       
   376       // https://github.com/jimmywarting/FormData/issues/44
       
   377       if (data instanceof FormDataPolyfill) {
       
   378         const blob = data['_blob']()
       
   379         this.setRequestHeader('Content-Type', blob.type)
       
   380         _send.call(this, blob)
       
   381       } else {
       
   382         _send.call(this, data)
       
   383       }
       
   384     }
       
   385   }
       
   386 
       
   387   // Patch fetch's function to call _blob transparently
       
   388   if (_fetch) {
       
   389     const _fetch = global.fetch
       
   390 
       
   391     global.fetch = function(input, init) {
       
   392       if (init && init.body && init.body instanceof FormDataPolyfill) {
       
   393         init.body = init.body['_blob']()
       
   394       }
       
   395 
       
   396       return _fetch(input, init)
       
   397     }
       
   398   }
       
   399 
       
   400   global['FormData'] = FormDataPolyfill
       
   401 }