Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1248. Handle blobs in addition to data URIs in bugform.js #1253

Merged
merged 2 commits into from
Dec 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

This comment was marked as abuse.

} 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