From d8d43a427a549cf063de3f6b3c22a08a06f53ffa Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 18 Nov 2020 18:01:58 +0100 Subject: [PATCH] Fix image uploads being random data when canvas read access is blocked (#15180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #15178 PR #11499 introduced a way to deal with all-white canvas, but newer Firefox versions set random data instead. This PR detects whether canvas operations are reliable by comparing the results on a hardcoded 2×2 pixels image, and memoizing the result. This should be both more reliable and faster than the previous check. --- app/javascript/mastodon/utils/resize_image.js | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js index 6c1cb61a2..8f1485379 100644 --- a/app/javascript/mastodon/utils/resize_image.js +++ b/app/javascript/mastodon/utils/resize_image.js @@ -41,6 +41,45 @@ const dropOrientationIfNeeded = (orientation) => new Promise(resolve => { } }); +// Some browsers don't allow reading from a canvas and instead return all-white +// or randomized data. Use a pre-defined image to check if reading the canvas +// works. +const checkCanvasReliability = () => new Promise((resolve, reject) => { + switch(_browser_quirks['canvas-read-unreliable']) { + case true: + reject('Canvas reading unreliable'); + break; + case false: + resolve(); + break; + default: + // 2×2 GIF with white, red, green and blue pixels + const testImageURL = + 'data:image/gif;base64,R0lGODdhAgACAKEDAAAA//8AAAD/AP///ywAAAAAAgACAAACA1wEBQA7'; + const refData = + [255, 255, 255, 255, 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, 2, 2); + const imageData = context.getImageData(0, 0, 2, 2); + if (imageData.data.every((x, i) => refData[i] === x)) { + _browser_quirks['canvas-read-unreliable'] = false; + resolve(); + } else { + _browser_quirks['canvas-read-unreliable'] = true; + reject('Canvas reading unreliable'); + } + }; + img.onerror = () => { + _browser_quirks['canvas-read-unreliable'] = true; + reject('Failed to load test image'); + }; + img.src = testImageURL; + } +}); + const getImageUrl = inputFile => new Promise((resolve, reject) => { if (window.URL && URL.createObjectURL) { try { @@ -110,14 +149,6 @@ const processImage = (img, { width, height, orientation, type = 'image/png' }) = context.drawImage(img, 0, 0, width, height); - // The Tor Browser and maybe other browsers may prevent reading from canvas - // and return an all-white image instead. Assume reading failed if the resized - // image is perfectly white. - const imageData = context.getImageData(0, 0, width, height); - if (imageData.data.every(value => value === 255)) { - throw 'Failed to read from canvas'; - } - canvas.toBlob(resolve, type); }); @@ -127,7 +158,8 @@ const resizeImage = (img, type = 'image/png') => new Promise((resolve, reject) = const newWidth = Math.round(Math.sqrt(MAX_IMAGE_PIXELS * (width / height))); const newHeight = Math.round(Math.sqrt(MAX_IMAGE_PIXELS * (height / width))); - getOrientation(img, type) + checkCanvasReliability() + .then(getOrientation(img, type)) .then(orientation => processImage(img, { width: newWidth, height: newHeight,