mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
002d3c179d
Summary: All struct args are passed into NativeModule methods via references. If blocks access those references, we don't move those references to the heap. This means that by the time that the block accesses the struct arg, it could be freed. This can crash the program. The solution is simple: we copy the struct arg, and access the copy in the block. This ensures that the block will make a copy, which prevents the underlying data structures from being released by the time that the block accesses the struct arg. Changelog: [iOS][Fixed] - Retain cropData struct arg in ImageEditingManager.cropImage call Differential Revision: D18076026 fbshipit-source-id: 1a7bb602606ff1afac38ad5451662c82fa86f205
106 lines
3.6 KiB
Plaintext
106 lines
3.6 KiB
Plaintext
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTImageEditingManager.h>
|
|
|
|
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTImageLoader.h>
|
|
#import <React/RCTImageStoreManager.h>
|
|
#import <React/RCTImageUtils.h>
|
|
#import <React/RCTImageLoaderProtocol.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import "RCTImagePlugins.h"
|
|
|
|
@interface RCTImageEditingManager() <NativeImageEditorSpec>
|
|
@end
|
|
|
|
@implementation RCTImageEditingManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
/**
|
|
* Crops an image and adds the result to the image store.
|
|
*
|
|
* @param imageRequest An image URL
|
|
* @param cropData Dictionary with `offset`, `size` and `displaySize`.
|
|
* `offset` and `size` are relative to the full-resolution image size.
|
|
* `displaySize` is an optimization - if specified, the image will
|
|
* be scaled down to `displaySize` rather than `size`.
|
|
* All units are in px (not points).
|
|
*/
|
|
RCT_EXPORT_METHOD(cropImage:(NSURLRequest *)imageRequest
|
|
cropData:(JS::NativeImageEditor::Options &)cropData
|
|
successCallback:(RCTResponseSenderBlock)successCallback
|
|
errorCallback:(RCTResponseSenderBlock)errorCallback)
|
|
{
|
|
CGRect rect = {
|
|
[RCTConvert CGPoint:@{
|
|
@"x": @(cropData.offset().x()),
|
|
@"y": @(cropData.offset().y()),
|
|
}],
|
|
[RCTConvert CGSize:@{
|
|
@"width": @(cropData.size().width()),
|
|
@"height": @(cropData.size().height()),
|
|
}]
|
|
};
|
|
|
|
// We must keep a copy of cropData so that we can access data from it at a later time
|
|
JS::NativeImageEditor::Options cropDataCopy = cropData;
|
|
|
|
[[_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES]
|
|
loadImageWithURLRequest:imageRequest callback:^(NSError *error, UIImage *image) {
|
|
if (error) {
|
|
errorCallback(@[RCTJSErrorFromNSError(error)]);
|
|
return;
|
|
}
|
|
|
|
// Crop image
|
|
CGSize targetSize = rect.size;
|
|
CGRect targetRect = {{-rect.origin.x, -rect.origin.y}, image.size};
|
|
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetRect);
|
|
UIImage *croppedImage = RCTTransformImage(image, targetSize, image.scale, transform);
|
|
|
|
// Scale image
|
|
if (cropDataCopy.displaySize()) {
|
|
targetSize = [RCTConvert CGSize:@{@"width": @(cropDataCopy.displaySize()->width()), @"height": @(cropDataCopy.displaySize()->height())}]; // in pixels
|
|
RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:cropDataCopy.resizeMode() ?: @"contain"];
|
|
targetRect = RCTTargetRect(croppedImage.size, targetSize, 1, resizeMode);
|
|
transform = RCTTransformFromTargetRect(croppedImage.size, targetRect);
|
|
croppedImage = RCTTransformImage(croppedImage, targetSize, image.scale, transform);
|
|
}
|
|
|
|
// Store image
|
|
[self->_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) {
|
|
if (!croppedImageTag) {
|
|
NSString *errorMessage = @"Error storing cropped image in RCTImageStoreManager";
|
|
RCTLogWarn(@"%@", errorMessage);
|
|
errorCallback(@[RCTJSErrorFromNSError(RCTErrorWithMessage(errorMessage))]);
|
|
return;
|
|
}
|
|
successCallback(@[croppedImageTag]);
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:
|
|
(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
|
{
|
|
return std::make_shared<facebook::react::NativeImageEditorSpecJSI>(self, jsInvoker);
|
|
}
|
|
|
|
@end
|
|
|
|
Class RCTImageEditingManagerCls() {
|
|
return RCTImageEditingManager.class;
|
|
}
|