My team and I are unhappy that our amazing animated application doesn’t work in Safari due to a combination of Safari and AppEngine restrictions. We hope that one of you will help us find a “magic spell” to solve it.
This can be very easy to solve, but after two days we encountered only brick walls, as this is an unusual (albeit very useful) scenario, which is largely undocumented.
Let me explain the details of the problem.
Our application should save the canvas data to the blog repository (images that the user drew for his animation.) This can usually be done by placing the web form dynamically through ajax, in which there is a binary field with image data. One way to do this is to use ArrayBuffer and BlobBuilder. This works with Chrome:
dataURItoBlob = function(dataURI, callback) { var ab, bb, byteString, i, ia, mimeString, _ref; if (!(typeof ArrayBuffer != "undefined" && ArrayBuffer !== null)) { return null; } byteString = atob(dataURI.split(',')[1]); mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; ab = new ArrayBuffer(byteString.length); ia = new Uint8Array(ab); for (i = 0, _ref = byteString.length; (0 <= _ref ? i < _ref : i > _ref); (0 <= _ref ? i += 1 : i -= 1)) { ia[i] = byteString.charCodeAt(i); } bb = window.BlobBuilder ? new BlobBuilder() : window.WebKitBlobBuilder ? new WebKitBlobBuilder() : window.MozBlobBuilder ? new MozBlobBuilder() : void 0; if (bb != null) { bb.append(ab); return bb.getBlob(mimeString); } else { return null; } }; postCanvasToBlobstore = function(url, name, canvas) { blob = dataURItoBlob(canvas.toDataURL()); formData = new FormData(); formData.append("file", blob); xhr = new XMLHttpRequest(); xhr.open("POST", url); return xhr.send(formData); }
Another way to save data in binary form is to use xhr.sendAsBinary (). This works for Firefox:
postCanvasToBlobstore = function(url, name, canvas) { type='image/png' var arr, boundary, data, j, xhr; data = canvas.toDataURL(type); data = data.replace('data:' + type + ';base64,', ''); xhr = new XMLHttpRequest(); xhr.open('POST', url, true); boundary = 'imaboundary'; xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); arr = ['--' + boundary, 'Content-Disposition: form-data; name="' + name + '"; filename="' + name + '"', 'Content-Type: ' + type, '', atob(data), '--' + boundary + '--']; j = arr.join('\r\n'); return xhr.sendAsBinary(j); }
It seems that none of these features exist for Safari (although it is quite possible, but we are not smart enough to understand this). One alternative is to just use base64 encoded data, which Safari can do for sure. Here's how it would look:
postCanvasToBlobstore = function(url, name, canvas, type) { type='image/png' var arr, boundary, data, j, xhr; data = canvas.toDataURL(type); data = data.replace('data:' + type + ';base64,', ''); xhr = new XMLHttpRequest(); xhr.open('POST', url, true); boundary = 'imaboundary'; xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); arr = ['--' + boundary, 'Content-Disposition: form-data; name="' + name + '"; filename="' + name + '"', 'Content-Transfer-Encoding: base64','Content-Type: ' + type, '', data, '--' + boundary + '--']; j = arr.join('\r\n'); return xhr.send(j); }
Now it really works! However, it only works with the AppEngine tool development version due to a known bug in the blob storage: at the moment you launch the application into production, it stops working. Of course, it is possible that setting up the POST code somewhere may solve the problem that blobstore encounters when interpreting the data. See Http://code.google.com/p/googleappengine/issues/detail?id=4265 for what appears to be an online store issue related to this issue.
You can paste any of the three code examples above into the edit control and see what happens with each version of the postCanvasToBlobstore function: the first example will work in Chrome, the second will work in Firefox (and is the default version used for the debugging application). j), and the third should work out of all three (but does not work for any of them when using this production website, possibly due to a store error.)