jimmywarting / FormData

HTML5 `FormData` polyfill for Browsers and nodejs
MIT License
360 stars 102 forks source link

Don't require _blob(), or suggest workaround in readme #28

Closed tremby closed 6 years ago

tremby commented 7 years ago

I'm using fetch (no polyfill, haven't "fixed" it -- don't know how and don't see any docs on that), and sending body: formData. This was working natively in Chrome and Firefox and Safari, but I wanted the FormData polyfill so that I could use formData.delete and the other methods. After installing the polyfill, the fetch no longer worked in Safari: it was sending the string [object FormData] rather than the actual data. I realized after reading the docs again that I needed to use body: formData._blob().

This is a shame, since it means I can't selectively activate the polyfill, only when needed, while leaving my source code alone.

  1. Is there no way for this polyfill to serialize itself as the blob when required, in effect transparently calling its _blob method, so that no code change is needed?
  2. Failing that, may I suggest a note in the readme recommending code such as I'm using now, which is body: formData._blob ? formData._blob() : formData?
jimmywarting commented 7 years ago

There has been a couple of requests to patch xhr and fetch to transparently calling its _blob method. will see if i can take a look at it later. Need to patch both xhr and fetch.

tremby commented 7 years ago

Ah, I was hoping it'd be as simple as prototype.toString = prototype._blob or something.

jimmywarting commented 7 years ago

Ah, I was hoping it'd be as simple as prototype.toString = prototype._blob or something.

If only it was that simple... Reason I have held back is cuz I wanted it to be focus to just be about a FormData and not so much about anything else regarding of how it's consumed by other API's such as fetch and/or XHR. But there has been a couple of people asking for transparencies now

tremby commented 7 years ago

Yeah, I think the fact is that anyone using FormData is very likely to be then sending it via either XHR or fetch. And speaking for myself at least, the reason I use a polyfill at all is that I want to use the APIs the standards describe, with no code changes at all except loading the polyfill if I can get away with it. If that's not possible, the polyfill sadly isn't quite fulfilling its purpose, as I see it.

Incidentally, if you do proceed with patching fetch, please make sure it's compatible with the whatwg-fetch polyfill. (Presumably that one would have to be loaded before the FormData polyfill, if using both.)

jimmywarting commented 7 years ago

Will take it into consideration.

jimmywarting commented 7 years ago

fetch polyfill is based on xhr, So don't need to worry so much about that

jimmywarting commented 7 years ago

Would you do me the honor of testing this for me?

var f,l="function"==typeof Object.defineProperties?Object.defineProperty:function(b,a,d){b!=Array.prototype&&b!=Object.prototype&&(b[a]=d.value)},m="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this;function n(){n=function(){};m.Symbol||(m.Symbol=p)}var p=function(){var b=0;return function(a){return"jscomp_symbol_"+(a||"")+b++}}();
function q(){n();var b=m.Symbol.iterator;b||(b=m.Symbol.iterator=m.Symbol("iterator"));"function"!=typeof Array.prototype[b]&&l(Array.prototype,b,{configurable:!0,writable:!0,value:function(){return t(this)}});q=function(){}}function t(b){var a=0;return u(function(){return a<b.length?{done:!1,value:b[a++]}:{done:!0}})}function u(b){q();b={next:b};b[m.Symbol.iterator]=function(){return this};return b}function v(b){q();n();q();var a=b[Symbol.iterator];return a?a.call(b):t(b)}
function w(b){for(var a,d=[];!(a=b.next()).done;)d.push(a.value);return d}
if(!window.FormData||!window.FormData.prototype.get){var y=function(b,a,d){if(2>arguments.length)throw new TypeError("2 arguments required, but only "+arguments.length+" present.");return a instanceof Blob?[b+"",a,void 0!==d?d+"":"File"===Object.prototype.toString.call(a).slice(8,-1)?a.name:"Blob"]:[b+"",a+""]},A=function(b){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");return[b+""]},B=function(b){var a=v(b);b=a.next().value;a=a.next().value;b instanceof Blob&&
(b=new File([b],a,{type:b.type,lastModified:b.lastModified}));return b},C=window.FormData,D=window.XMLHttpRequest.prototype.send,E=window.Request&&window.fetch;n();var F=window.Symbol&&Symbol.toStringTag,G=new WeakMap;F&&(Blob.prototype[F]||(Blob.prototype[F]="Blob"),"File"in window&&!File.prototype[F]&&(File.prototype[F]="File"));try{new File([],"")}catch(b){window.File=function(a,d,c){a=new Blob(a,c);c=c&&void 0!==c.lastModified?new Date(c.lastModified):new Date;Object.defineProperties(a,{name:{value:d},
lastModifiedDate:{value:c},lastModified:{value:+c},toString:{value:function(){return"[object File]"}}});F&&Object.defineProperty(a,F,{value:"File"});return a}}var H=function(b){G.set(this,Object.create(null));if(!b)return this;b=v(Array.from(b.elements));for(var a=b.next();!a.done;a=b.next()){var d=a.value;a=d.name;var c=d.type,e=d.value,g=d.files,k=d.checked;d=d.selectedOptions;if(a)if("file"===c)for(c=v(g),e=c.next();!e.done;e=c.next())this.append(a,e.value);else if("select-multiple"===c||"select-one"===
c)for(c=v(Array.from(d)),e=c.next();!e.done;e=c.next())this.append(a,e.value.value);else"checkbox"===c||"radio"===c?k&&this.append(a,e):this.append(a,e)}};f=H.prototype;f.append=function(b,a,d){var c=G.get(this);c[b]||(c[b]=[]);c[b].push([a,d])};f["delete"]=function(b){delete G.get(this)[b]};f.entries=function(){function b(b,x){for(;;)switch(a){case 0:z=G.get(N);h=[];k=z;for(g in k)h.push(g);r=0;case 1:if(!(r<h.length)){a=3;break}g=h[r];if(g in k){a=4;break}a=2;break;case 4:e=v(z[g]),c=e.next();case 5:if(c.done){a=
7;break}d=c.value;a=8;return{value:[g,B(d)],done:!1};case 8:if(1!=b){a=9;break}a=-1;throw x;case 9:case 6:c=e.next();a=5;break;case 7:case 2:r++;a=1;break;case 3:a=-1;default:return{value:void 0,done:!0}}}var a=0,d,c,e,g,k,r,h,z,N=this,x={next:function(){return b(0,void 0)},"throw":function(a){return b(1,a)},"return":function(){throw Error("Not yet implemented");}};q();x[Symbol.iterator]=function(){return this};return x};f.forEach=function(b,a){for(var d=v(this),c=d.next();!c.done;c=d.next()){var e=
v(c.value);c=e.next().value;e=e.next().value;b.call(a,e,c,this)}};f.get=function(b){var a=G.get(this);return a[b]?B(a[b][0]):null};f.getAll=function(b){return(G.get(this)[b]||[]).map(B)};f.has=function(b){return b in G.get(this)};f.keys=function(){function b(b,h){for(;;)switch(a){case 0:k=v(r),g=k.next();case 1:if(g.done){a=3;break}e=g.value;c=v(e);d=c.next().value;a=4;return{value:d,done:!1};case 4:if(1!=b){a=5;break}a=-1;throw h;case 5:case 2:g=k.next();a=1;break;case 3:a=-1;default:return{value:void 0,
done:!0}}}var a=0,d,c,e,g,k,r=this,h={next:function(){return b(0,void 0)},"throw":function(a){return b(1,a)},"return":function(){throw Error("Not yet implemented");}};q();h[Symbol.iterator]=function(){return this};return h};f.set=function(b,a,d){G.get(this)[b]=[[a,d]]};f.values=function(){function b(b,h){for(;;)switch(a){case 0:k=v(r),g=k.next();case 1:if(g.done){a=3;break}e=g.value;c=v(e);c.next();d=c.next().value;a=4;return{value:d,done:!1};case 4:if(1!=b){a=5;break}a=-1;throw h;case 5:case 2:g=
k.next();a=1;break;case 3:a=-1;default:return{value:void 0,done:!0}}}var a=0,d,c,e,g,k,r=this,h={next:function(){return b(0,void 0)},"throw":function(a){return b(1,a)},"return":function(){throw Error("Not yet implemented");}};q();h[Symbol.iterator]=function(){return this};return h};f.stream=function(){try{return this._blob().stream()}catch(b){throw Error("Include https://github.com/jimmywarting/Screw-FileReader for streaming support");}};H.prototype._asNative=function(){for(var b=new C,a=v(this),
d=a.next();!d.done;d=a.next()){var c=v(d.value);d=c.next().value;c=c.next().value;b.append(d,c)}return b};H.prototype._blob=function(){for(var b="----formdata-polyfill-"+Math.random(),a=[],d=v(this),c=d.next();!c.done;c=d.next()){var e=v(c.value);c=e.next().value;e=e.next().value;a.push("--"+b+"\r\n");e instanceof Blob?a.push('Content-Disposition: form-data; name="'+c+'"; filename="'+e.name+'"\r\n',"Content-Type: "+(e.type||"application/octet-stream")+"\r\n\r\n",e,"\r\n"):a.push('Content-Disposition: form-data; name="'+
c+'"\r\n\r\n'+e+"\r\n")}a.push("--"+b+"--");return new Blob(a,{type:"multipart/form-data; boundary="+b})};n();q();H.prototype[Symbol.iterator]=function(){return this.entries()};H.prototype.toString=function(){return"[object FormData]"};F&&(H.prototype[F]="FormData");for(var I={},J=v([["append",y],["delete",A],["get",A],["getAll",A],["has",A],["set",y]]),K=J.next();!K.done;I={b:I.b,a:I.a},K=J.next()){var L=v(K.value),M=L.next().value;I.a=L.next().value;I.b=H.prototype[M];H.prototype[M]=function(b){return function(){return b.b.apply(this,
b.a.apply(null,[].concat(arguments instanceof Array?arguments:w(v(arguments)))))}}(I)}XMLHttpRequest.prototype.send=function(b){D.call(this,b instanceof H?b.c():b)};if(E){var O=window.fetch;window.fetch=function(b,a){a&&a.body&&a.body instanceof H&&(a.body=a.body.c());return O(b,a)}}window.FormData=H};

It's minified with Closure Compiler adv. mode The differences in this is that it don't have a module.export it only has to be required or loaded in just as it's (result in easier hosting options such as cdnjs) I'm patching fetch and xhr and also adding the polyfill to the global scope.

jimmywarting commented 7 years ago

Only thing i didn't do is patching Request constructor. so this don't work:

req = new Request(url, {method: 'post', body: formdata}) // must call _blob here
fetch(request)
tremby commented 7 years ago

I'm testing with this: https://jsfiddle.net/sofj9c3h/3/

This is only testing without the whatwg-fetch polyfill so far.

In Firefox FormData is not overridden (that's fine with me -- I want it overridden only when its append or delete or entries or other such methods don't exist). It then deletes and adds and loops through entries as expected, and starts the fetch OK (which then fails, as expected, since I'm not posting to any real endpoint).

In Safari on desktop on a Mac, it's overridden (correct), deletes and adds and loops through entries as expected, but fails to start the fetch:

a.body.c is not a function. (In 'a.body.c()', 'a.body.c' is undefined)
jimmywarting commented 7 years ago

Was only one tiny mistake, Closure Compiler mangle properties starting with _blob() had to wrap it into body['_blob']()

Tested it myself and seems to work okey, probably going to make a new release later today, need to update the readme also.

jimmywarting commented 7 years ago

just one tip: if you are testing http request https://httpbin.org is a good choice Allows cors request and echo everything back that you send to it

tremby commented 7 years ago

Good to know; thanks. But in this case I'd know whether the test passed or failed whether or not the actual request succeeded.

jimmywarting commented 7 years ago

The test/request passed, just have to publish it to npm first, will do that later... Meanwhile I would appreciate if you could test it and also edit the readme.md for grammar, before i publish it.

Thanks in advance and for using the formdata polyfill

tremby commented 7 years ago

The test/request passed, just have to publish it to npm first, will do that later...

You misunderstood me here. I was just saying that while httpbin is useful to know about, I didn't need it for my purposes here -- the jsfiddle test case is conclusive even with a failing request. Anyway, this doesn't matter.

Meanwhile I would appreciate if you could test

Seems to work fine for me.

also edit the readme.md for grammar, before i publish it.

Sure. I'll open a pull request. (Done: #30)