Skip to content

Commit

Permalink
Merge pull request #1253 from /issues/1248/1
Browse files Browse the repository at this point in the history
Fixes #1248. Handle blobs in addition to data URIs in bugform.js
  • Loading branch information
Mike Taylor authored Dec 23, 2016
2 parents 854f30b + e4253f6 commit a6cdde4
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 28 deletions.
72 changes: 70 additions & 2 deletions tests/functional/image-uploads-non-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*global Promise:true*/

define([
'intern',
'intern!object',
Expand Down Expand Up @@ -30,6 +32,38 @@ define([
.end();
},

'postMessaged blob preview': function() {
return this.remote
.setFindTimeout(intern.config.wc.pageLoadTimeout)
.get(require.toUrl(url + '?open=1'))
// Build up a green test square in canvas, toBlob that, and then postMessage the blob
.execute(function() {
return new Promise(function(res) {
var c = document.createElement('canvas');
c.width = 25;
c.height = 25;
var ctx = c.getContext('2d');
ctx.fillStyle = 'rgb(0, 128, 0)';
ctx.rect(0, 0, 25, 25);
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.strokeRect(0, 0, 25, 25);
c.toBlob(function(blob) {
res(blob);
});
}).then(function(blob) {
postMessage(blob, 'http://localhost:5000');
});
})
.sleep(1000)
.findByCssSelector('.js-image-upload-label').getAttribute('style')
.then(function(inlineStyle) {
assert.include(inlineStyle, 'data:image/png;base64,iVBOR', 'Base64 data shown as preview background');
})
.end();
},

'postMessaged dataURI image upload worked': function() {
return this.remote
.setFindTimeout(intern.config.wc.pageLoadTimeout)
Expand All @@ -44,11 +78,45 @@ define([
.end();
},

'postMessaged dataURI remove button': function() {
'postMessaged blob image upload worked': function() {
return this.remote
.setFindTimeout(intern.config.wc.pageLoadTimeout)
.get(require.toUrl(url + '?open=1'))
// Build up a green test square in canvas, toBlob that, and then postMessage the blob
.execute(function() {
return new Promise(function(res) {
var c = document.createElement('canvas');
c.width = 25;
c.height = 25;
var ctx = c.getContext('2d');
ctx.fillStyle = 'rgb(0, 128, 0)';
ctx.rect(0, 0, 25, 25);
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.strokeRect(0, 0, 25, 25);
c.toBlob(function(blob) {
res(blob);
});
}).then(function(blob) {
postMessage(blob, 'http://localhost:5000');
});
})
.sleep(1000)
.findByCssSelector('#description').getProperty('value')
.then(function(val) {
assert.include(val, '![Screenshot Description](http://localhost:5000/uploads/', 'The data URI was correctly uploaded and its URL was copied to the bug description.');
})
.end();
},

'remove image upload button': function() {
return this.remote
.setFindTimeout(intern.config.wc.pageLoadTimeout)
.get(require.toUrl(url + '?open=1'))
// send a small base64 encoded green test square
// in theory a blob should work as well, since by the time we're removing the image,
// it's been converted to a data URI
.execute('postMessage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAIAAABLixI0AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3gYSAig452t/EQAAAClJREFUOMvtzkENAAAMg0A25ZU+E032AQEXoNcApCGFLX5paWlpaWl9dqq9AS6CKROfAAAAAElFTkSuQmCC", "http://localhost:5000")')
.sleep(1000)
.findByCssSelector('.js-image-upload-label .wc-UploadForm-button').isDisplayed()
Expand All @@ -65,7 +133,7 @@ define([
.end()
.findByCssSelector('#description').getProperty('value')
.then(function(val) {
assert.notInclude(val, '![Screenshot Description](http://localhost:5000/uploads/', 'The data URI was correctly uploaded and its URL was copied to the bug description.');
assert.notInclude(val, '![Screenshot Description](http://localhost:5000/uploads/', 'The url to the image upload was correctly removed.');
})
.end();
}
Expand Down
57 changes: 31 additions & 26 deletions webcompat/static/js/lib/bugform.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ function BugForm() {
this.reportButton = $('#js-ReportBug');
this.loaderImage = $('.js-Loader');
this.uploadLoader = $('.js-Upload-Loader');
this.screenshotData = '';
// by default, submission type is anonymous
this.submitType = 'github-proxy-report';
this.UPLOAD_LIMIT = 1024 * 1024 * 4;
Expand Down Expand Up @@ -77,23 +76,34 @@ function BugForm() {
// Set up listener for message events from screenshot-enabled add-ons
window.addEventListener('message', _.bind(function(event) {
// Make sure the data is coming from ~*inside the house*~!
// (i.e., our add-on sent it)
// (i.e., our add-on or some other priviledged code sent it)
if (location.origin === event.origin) {
this.screenshotData = event.data;

// The final size of Base64-encoded binary data is ~equal to
// 1.37 times the original data size + 814 bytes (for headers).
// so, bytes = (encoded_string.length - 814) / 1.37)
// see https://en.wikipedia.org/wiki/Base64#MIME
if ((String(this.screenshotData).length - 814 / 1.37) > this.UPLOAD_LIMIT) {
this.downsampleImageAndUpload(this.screenshotData);
// See https://github.com/webcompat/webcompat.com/issues/1252 to track
// the work of only accepting blobs, which should simplify things.
if (event.data instanceof Blob) {
// showUploadPreview will take care of converting from blob to
// dataURI, and will send the result to resampleIfNecessaryAndUpload.
this.showUploadPreview(event.data);
} else {
this.addPreviewBackgroundAndUpload(this.screenshotData);
// ...the data is already a data URI string
this.resampleIfNecessaryAndUpload(event.data);
}
}
}, this), false);
};

this.resampleIfNecessaryAndUpload = function(screenshotData) {
// The final size of Base64-encoded binary data is ~equal to
// 1.37 times the original data size + 814 bytes (for headers).
// so, bytes = (encoded_string.length - 814) / 1.37)
// see https://en.wikipedia.org/wiki/Base64#MIME
if ((String(screenshotData).length - 814 / 1.37) > this.UPLOAD_LIMIT) {
this.downsampleImageAndUpload(screenshotData);
} else {
this.addPreviewBackgroundAndUpload(screenshotData);
}
};

this.downsampleImageAndUpload = function(dataURI) {
var img = document.createElement('img');
var canvas = document.createElement('canvas');
Expand All @@ -110,16 +120,16 @@ function BugForm() {
// Note: this will convert GIFs to JPEG, which breaks
// animated GIFs. However, this only will happen if they
// were above the upload limit size. So... sorry?
this.screenshotData = canvas.toDataURL('image/jpeg', 0.8);
var screenshotData = canvas.toDataURL('image/jpeg', 0.8);

// The limit is 4MB (which is crazy big!), so let the user know if their
// file is unreasonably large at this point (after 1 round of downsampling)
if (this.screenshotData > this.UPLOAD_LIMIT) {
if (screenshotData > this.UPLOAD_LIMIT) {
this.makeInvalid('image', {altHelp: true});
return;
}

this.addPreviewBackgroundAndUpload(this.screenshotData);
this.addPreviewBackgroundAndUpload(screenshotData);
img = null, canvas = null;
}, this);

Expand Down Expand Up @@ -184,7 +194,9 @@ function BugForm() {
} else {
this.makeValid('image');
if (event) {
this.showUploadPreview(event);
// We can just grab the 0th one, because we only allow uploading
// a single image at a time (for now)
this.showUploadPreview(event.target.files[0]);
}
}
};
Expand Down Expand Up @@ -312,30 +324,23 @@ function BugForm() {
If the users browser understands the FileReader API, show a preview
of the image they're about to load.
*/
this.showUploadPreview = function(event) {
this.showUploadPreview = function(blobOrFile) {
if (!(window.FileReader && window.File)) {
return;
}
// We can just grab the 0th one, because we only allow uploading
// a single image at a time (for now)
var img = event.target.files[0];

// One last image type validation check.
if (!img.type.match('image.*')) {
if (!blobOrFile.type.match('image.*')) {
this.makeInvalid('image');
return;
}

var reader = new FileReader();
reader.onload = _.bind(function(event) {
var dataURI = event.target.result;
if ((String(dataURI).length - 814 / 1.37) > this.UPLOAD_LIMIT) {
this.downsampleImageAndUpload(dataURI);
} else {
this.addPreviewBackgroundAndUpload(dataURI);
}
this.resampleIfNecessaryAndUpload(dataURI);
}, this);
reader.readAsDataURL(img);
reader.readAsDataURL(blobOrFile);
};

this.addPreviewBackgroundAndUpload = function(dataURI) {
Expand Down

0 comments on commit a6cdde4

Please sign in to comment.