title: Saving canvas image via JavaScript from Safari (5.0.x) to AppEngine Blobstore
body: <p>My team and I are unhappy that our awesome animation app doesn't work on Safari because of a combination of Safari and AppEngine limitations. We're hoping one of you can help us find the "magic incantation" to solve it.</p>
  
  <p>This may be an extremely easy problem to solve, but we've hit only brick walls after two days, since this is an uncommon (though very useful) scenario that is pretty much undocumented.</p>
  
  <p>Let me explain the details of the problem.</p>
  
  <p>Our app needs to save canvas data to the blobstore (images the user has drawn for their animation.) Usually, you would do this by posting a web form dynamically via ajax that has a binary field in it with the image data. One way to do this is by using ArrayBuffer and BlobBuilder. This works with Chrome:</p>
  
  <pre><code>dataURItoBlob = function(dataURI, callback) {
    var ab, bb, byteString, i, ia, mimeString, _ref;
    if (!(typeof ArrayBuffer != "undefined" &amp;&amp; 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 &lt;= _ref ? i &lt; _ref : i &gt; _ref); (0 &lt;= _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);
  }
  </code></pre>
  
  <p>Another way to save binary form data is to use xhr.sendAsBinary(). This works for Firefox:</p>
  
  <pre><code>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);
  }
  </code></pre>
  
  <p>Neither of these possibilities seem to exist for Safari (though it's very possible it is, but we're not smart enough to figure it out). One alternative is to just use base64 encoded data, which Safari can do for sure. This is what that would look like:</p>
  
  <pre><code>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);
  }
  </code></pre>
  
  <p>Now this actually works! However, it only works on the development version of the AppEngine tools, because of a known blobstore bug: The moment you deploy your app into production it stops working. Of course, it's possible that tweaking the POSTing code somewhere may resolve the problem the blobstore is having interpreting the data. See <a href="http://code.google.com/p/googleappengine/issues/detail?id=4265" rel="nofollow noreferrer">http://code.google.com/p/googleappengine/issues/detail?id=4265</a> for what appears to be the blobstore issue connected with this problem.</p>
  
  <p>You can paste any of the three code samples above into the edit control and can see what happens with each version of the postCanvasToBlobstore function: the first sample will work in Chrome, the second will work in Firefox (and is the version the debug app defaults to), and the third should work from all three (but works for none of them when using this production website, probably because of a blobstore bug.)</p>