[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /* global FormData self Blob File */ 2 /* eslint-disable no-inner-declarations */ 3 4 if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) { 5 const global = typeof globalThis === 'object' 6 ? globalThis 7 : typeof window === 'object' 8 ? window 9 : typeof self === 'object' ? self : this 10 11 // keep a reference to native implementation 12 const _FormData = global.FormData 13 14 // To be monkey patched 15 const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send 16 const _fetch = global.Request && global.fetch 17 const _sendBeacon = global.navigator && global.navigator.sendBeacon 18 // Might be a worker thread... 19 const _match = global.Element && global.Element.prototype 20 21 // Unable to patch Request/Response constructor correctly #109 22 // only way is to use ES6 class extend 23 // https://github.com/babel/babel/issues/1966 24 25 const stringTag = global.Symbol && Symbol.toStringTag 26 27 // Add missing stringTags to blob and files 28 if (stringTag) { 29 if (!Blob.prototype[stringTag]) { 30 Blob.prototype[stringTag] = 'Blob' 31 } 32 33 if ('File' in global && !File.prototype[stringTag]) { 34 File.prototype[stringTag] = 'File' 35 } 36 } 37 38 // Fix so you can construct your own File 39 try { 40 new File([], '') // eslint-disable-line 41 } catch (a) { 42 global.File = function File (b, d, c) { 43 const blob = new Blob(b, c) 44 const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date() 45 46 Object.defineProperties(blob, { 47 name: { 48 value: d 49 }, 50 lastModifiedDate: { 51 value: t 52 }, 53 lastModified: { 54 value: +t 55 }, 56 toString: { 57 value () { 58 return '[object File]' 59 } 60 } 61 }) 62 63 if (stringTag) { 64 Object.defineProperty(blob, stringTag, { 65 value: 'File' 66 }) 67 } 68 69 return blob 70 } 71 } 72 73 function normalizeValue ([name, value, filename]) { 74 if (value instanceof Blob) { 75 // Should always returns a new File instance 76 // console.assert(fd.get(x) !== fd.get(x)) 77 value = new File([value], filename, { 78 type: value.type, 79 lastModified: value.lastModified 80 }) 81 } 82 83 return [name, value] 84 } 85 86 function ensureArgs (args, expected) { 87 if (args.length < expected) { 88 throw new TypeError(`$expected} argument required, but only $args.length} present.`) 89 } 90 } 91 92 function normalizeArgs (name, value, filename) { 93 return value instanceof Blob 94 // normalize name and filename if adding an attachment 95 ? [String(name), value, filename !== undefined 96 ? filename + '' // Cast filename to string if 3th arg isn't undefined 97 : typeof value.name === 'string' // if name prop exist 98 ? value.name // Use File.name 99 : 'blob'] // otherwise fallback to Blob 100 101 // If no attachment, just cast the args to strings 102 : [String(name), String(value)] 103 } 104 105 // normalize linefeeds for textareas 106 // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation 107 function normalizeLinefeeds (value) { 108 return value.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n') 109 } 110 111 function each (arr, cb) { 112 for (let i = 0; i < arr.length; i++) { 113 cb(arr[i]) 114 } 115 } 116 117 /** 118 * @implements {Iterable} 119 */ 120 class FormDataPolyfill { 121 /** 122 * FormData class 123 * 124 * @param {HTMLElement=} form 125 */ 126 constructor (form) { 127 this._data = [] 128 129 const self = this 130 131 form && each(form.elements, elm => { 132 if ( 133 !elm.name || 134 elm.disabled || 135 elm.type === 'submit' || 136 elm.type === 'button' || 137 elm.matches('form fieldset[disabled] *') 138 ) return 139 140 if (elm.type === 'file') { 141 const files = elm.files && elm.files.length 142 ? elm.files 143 : [new File([], '', { type: 'application/octet-stream' })] // #78 144 145 each(files, file => { 146 self.append(elm.name, file) 147 }) 148 } else if (elm.type === 'select-multiple' || elm.type === 'select-one') { 149 each(elm.options, opt => { 150 !opt.disabled && opt.selected && self.append(elm.name, opt.value) 151 }) 152 } else if (elm.type === 'checkbox' || elm.type === 'radio') { 153 if (elm.checked) self.append(elm.name, elm.value) 154 } else { 155 const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value 156 self.append(elm.name, value) 157 } 158 }) 159 } 160 161 /** 162 * Append a field 163 * 164 * @param {string} name field name 165 * @param {string|Blob|File} value string / blob / file 166 * @param {string=} filename filename to use with blob 167 * @return {undefined} 168 */ 169 append (name, value, filename) { 170 ensureArgs(arguments, 2) 171 this._data.push(normalizeArgs(name, value, filename)) 172 } 173 174 /** 175 * Delete all fields values given name 176 * 177 * @param {string} name Field name 178 * @return {undefined} 179 */ 180 delete (name) { 181 ensureArgs(arguments, 1) 182 const result = [] 183 name = String(name) 184 185 each(this._data, entry => { 186 entry[0] !== name && result.push(entry) 187 }) 188 189 this._data = result 190 } 191 192 /** 193 * Iterate over all fields as [name, value] 194 * 195 * @return {Iterator} 196 */ 197 * entries () { 198 for (var i = 0; i < this._data.length; i++) { 199 yield normalizeValue(this._data[i]) 200 } 201 } 202 203 /** 204 * Iterate over all fields 205 * 206 * @param {Function} callback Executed for each item with parameters (value, name, thisArg) 207 * @param {Object=} thisArg `this` context for callback function 208 * @return {undefined} 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 existen 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 normalizeValue(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(normalizeValue(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 * @return {undefined} 288 */ 289 set (name, value, filename) { 290 ensureArgs(arguments, 2) 291 name = String(name) 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 remaning 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 const chunks = [] 345 346 for (const [name, value] of this) { 347 chunks.push(`--$boundary}\r\n`) 348 349 if (value instanceof Blob) { 350 chunks.push( 351 `Content-Disposition: form-data; name="$name}"; filename="$value.name}"\r\n` + 352 `Content-Type: $value.type || 'application/octet-stream'}\r\n\r\n`, 353 value, 354 '\r\n' 355 ) 356 } else { 357 chunks.push( 358 `Content-Disposition: form-data; name="$name}"\r\n\r\n$value}\r\n` 359 ) 360 } 361 } 362 363 chunks.push(`--$boundary}--`) 364 365 return new Blob(chunks, { 366 type: 'multipart/form-data; boundary=' + boundary 367 }) 368 } 369 370 /** 371 * The class itself is iterable 372 * alias for formdata.entries() 373 * 374 * @return {Iterator} 375 */ 376 [Symbol.iterator] () { 377 return this.entries() 378 } 379 380 /** 381 * Create the default string description. 382 * 383 * @return {string} [object FormData] 384 */ 385 toString () { 386 return '[object FormData]' 387 } 388 } 389 390 if (_match && !_match.matches) { 391 _match.matches = 392 _match.matchesSelector || 393 _match.mozMatchesSelector || 394 _match.msMatchesSelector || 395 _match.oMatchesSelector || 396 _match.webkitMatchesSelector || 397 function (s) { 398 var matches = (this.document || this.ownerDocument).querySelectorAll(s) 399 var i = matches.length 400 while (--i >= 0 && matches.item(i) !== this) {} 401 return i > -1 402 } 403 } 404 405 if (stringTag) { 406 /** 407 * Create the default string description. 408 * It is accessed internally by the Object.prototype.toString(). 409 */ 410 FormDataPolyfill.prototype[stringTag] = 'FormData' 411 } 412 413 // Patch xhr's send method to call _blob transparently 414 if (_send) { 415 const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader 416 417 global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) { 418 setRequestHeader.call(this, name, value) 419 if (name.toLowerCase() === 'content-type') this._hasContentType = true 420 } 421 422 global.XMLHttpRequest.prototype.send = function (data) { 423 // need to patch send b/c old IE don't send blob's type (#44) 424 if (data instanceof FormDataPolyfill) { 425 const blob = data['_blob']() 426 if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type) 427 _send.call(this, blob) 428 } else { 429 _send.call(this, data) 430 } 431 } 432 } 433 434 // Patch fetch's function to call _blob transparently 435 if (_fetch) { 436 global.fetch = function (input, init) { 437 if (init && init.body && init.body instanceof FormDataPolyfill) { 438 init.body = init.body['_blob']() 439 } 440 441 return _fetch.call(this, input, init) 442 } 443 } 444 445 // Patch navigator.sendBeacon to use native FormData 446 if (_sendBeacon) { 447 global.navigator.sendBeacon = function (url, data) { 448 if (data instanceof FormDataPolyfill) { 449 data = data['_asNative']() 450 } 451 return _sendBeacon.call(this, url, data) 452 } 453 } 454 455 global['FormData'] = FormDataPolyfill 456 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Apr 19 01:00:04 2021 | Cross-referenced by PHPXref 0.7.1 |