Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Avoid re-encoding images when uploading local files (#31457)
Summary: Fixes #27099 When you upload a local file using XHR + the `FormData` API, RN uses `RCTNetworkTask` to retrieve the image file data from the local filesystem (request URL is a file:// URL) ([code pointer](https://github.com/facebook/react-native/blob/master/Libraries/Network/RCTNetworking.mm#L398)). As a result, if you are uploading a local image file that is in the app's directory `RCTNetworkTask` will end up using `RCTLocalAssetImageLoader` to load the image, which reads the image into a `UIImage` and then re-encodes it using `UIImageJPEGRepresentation` with a compression quality of 1.0, which is the higest ([code pointer](https://github.com/facebook/react-native/blob/4c5182c1cc8bafb15490adf602c87cb5bf289ffd/Libraries/Image/RCTImageLoader.mm#L1114)). Not only is this unnecessary, it ends up inflating the size of the jpg if it had been previously compressed to a lower quality. With this PR, this issue is fixed by forcing the `RCTFileRequestHandler` to be used when retrieving local files for upload, regardless of whether they are images or not. As a result, any file to be uploaded gets read into `NSData` which is the format needed when appending to the multipart body. I considered fixing this by modifying the behavior of how the handlers were chosen, but this felt like a safer fix since it will be scoped to just uploads and wont affect image fetching. ## Changelog [iOS] [Fixed] - Avoid re-encoding images when uploading local files Pull Request resolved: #31457 Test Plan: The repro for this is a bit troublesome, especially because this issue doesn't repro in RNTester. There is [some code](https://github.com/facebook/react-native/blob/master/packages/rn-tester/RNTester/AppDelegate.mm#L220) that is to be overriding the handlers that will be used, excluding the `RCTImageLoader`. I had to repro this in a fresh new RN app. 1. Create a blank RN app 2. Put an image in the folder of the app's install location. This would be similar to where files might be placed after an app downloads or captures an image. 3. Set up a quick express server that accepts multipart form uploads and stores the files 4. Trigger an upload via react native ``` const data = new FormData(); data.append('image', { uri: '/Users/arthur.lee/Library/Developer/CoreSimulator/Devices/46CDD981 (d0c8cb12f21604fd9730e275a52816d7fd00a826)-9164-4925-9025-1A76C0D9 (1946aee3d9696384d38890269ea705cafd472827)F0F5/data/Containers/Bundle/Application/B1E8A764-6221-4EA9-BE9A-2CB1699FD218 (1c92b1cff623ea3f3b78238b146ab001626ef305)/test.app/test.bundle/compressed.jpg', type: 'image/jpeg', name: 'image.jpeg', }); fetch(`http://localhost:3000/upload`, { method: 'POST', headers: {'Content-Type': 'multipart/form-data'}, body: data, }).then(console.log); ``` 5. Trigger the upload with and without this patch Original file: ``` $ ls -lh total 448 -rw-r--r-- 1 arthur.lee staff 223K Apr 29 17:08 compressed.jpg ``` Uploaded file (with and without patch): ``` $ ls -lh total 1624 -rw-r--r--@ 1 arthur.lee staff 584K Apr 29 17:11 image-nopatch.jpeg -rw-r--r--@ 1 arthur.lee staff 223K Apr 29 17:20 image-withpatch.jpeg ``` Would appreciate pointers on whether this needs to be tested more extensively Reviewed By: yungsters Differential Revision: D28630805 Pulled By: PeteTheHeat fbshipit-source-id: 606a6091fa3e817966548c5eb84b19cb8b9abb1c
- Loading branch information