Skip to content

Commit 26ff8c2

Browse files
committedJan 18, 2022
Fix RCTModalHostViewController crash while presenting react-native modals (wix#7423)
In some specific (and hard to reproduce) cases, RN modal presentation block tries to present a modal that is already presented in the hierarchy which caused that crash. Addresses facebook/react-native#32366
1 parent b8d7472 commit 26ff8c2

9 files changed

+115
-31
lines changed
 

‎lib/ios/RNNBridgeManager.mm

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
#import "RNNComponentViewCreator.h"
55
#import "RNNEventEmitter.h"
66
#import "RNNLayoutManager.h"
7+
#import "RNNModalHostViewManagerHandler.h"
78
#import "RNNReactComponentRegistry.h"
89
#import "RNNReactRootViewCreator.h"
910
#import "RNNSplashScreen.h"
1011
#import <React/RCTBridge.h>
11-
#import <React/RCTModalHostViewManager.h>
1212
#import <React/RCTUIManager.h>
1313

1414
@interface RNNBridgeManager ()
@@ -19,6 +19,7 @@ @interface RNNBridgeManager ()
1919
@property(nonatomic, strong, readonly) RNNLayoutManager *layoutManager;
2020
@property(nonatomic, strong, readonly) RNNOverlayManager *overlayManager;
2121
@property(nonatomic, strong, readonly) RNNModalManager *modalManager;
22+
@property(nonatomic, strong, readonly) RNNModalHostViewManagerHandler *modalHostViewHandler;
2223

2324
@end
2425

@@ -66,7 +67,8 @@ - (void)registerExternalComponent:(NSString *)name callback:(RNNExternalViewCrea
6667
[[RNNModalManagerEventHandler alloc] initWithEventEmitter:eventEmitter];
6768
_modalManager = [[RNNModalManager alloc] initWithBridge:bridge
6869
eventHandler:modalManagerEventHandler];
69-
70+
_modalHostViewHandler =
71+
[[RNNModalHostViewManagerHandler alloc] initWithModalManager:_modalManager];
7072
_layoutManager = [[RNNLayoutManager alloc] init];
7173

7274
id<RNNComponentViewCreator> rootViewCreator =
@@ -105,7 +107,7 @@ - (void)onJavaScriptWillLoad {
105107

106108
- (void)onJavaScriptLoaded {
107109
[_commandsHandler setReadyToReceiveCommands:true];
108-
[_modalManager
110+
[_modalHostViewHandler
109111
connectModalHostViewManager:[self.bridge moduleForClass:RCTModalHostViewManager.class]];
110112
[[_bridge moduleForClass:[RNNEventEmitter class]] sendOnAppLaunched];
111113
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import "RNNModalManager.h"
2+
#import <Foundation/Foundation.h>
3+
#import <React/RCTModalHostViewManager.h>
4+
5+
@interface RNNModalHostViewManagerHandler : NSObject
6+
7+
- (instancetype)initWithModalManager:(RNNModalManager *)modalManager;
8+
9+
- (void)connectModalHostViewManager:(RCTModalHostViewManager *)modalHostViewManager;
10+
11+
@end
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#import "RNNModalHostViewManagerHandler.h"
2+
3+
@implementation RNNModalHostViewManagerHandler {
4+
RNNModalManager *_modalManager;
5+
}
6+
7+
- (instancetype)initWithModalManager:(RNNModalManager *)modalManager {
8+
self = [super init];
9+
_modalManager = modalManager;
10+
return self;
11+
}
12+
13+
- (void)connectModalHostViewManager:(RCTModalHostViewManager *)modalHostViewManager {
14+
modalHostViewManager.presentationBlock =
15+
^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated,
16+
dispatch_block_t completionBlock) {
17+
if (reactViewController.presentedViewController != viewController &&
18+
[self->_modalManager topPresentedVC] != viewController) {
19+
[self->_modalManager showModal:viewController
20+
animated:animated
21+
completion:^(NSString *_Nonnull componentId) {
22+
if (completionBlock)
23+
completionBlock();
24+
}];
25+
}
26+
};
27+
28+
modalHostViewManager.dismissalBlock =
29+
^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated,
30+
dispatch_block_t completionBlock) {
31+
[self->_modalManager dismissModal:viewController
32+
animated:animated
33+
completion:^{
34+
if (completionBlock)
35+
completionBlock();
36+
}];
37+
};
38+
}
39+
40+
@end

‎lib/ios/RNNModalManager.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#import "RNNModalManagerEventHandler.h"
22
#import <Foundation/Foundation.h>
33
#import <React/RCTBridge.h>
4-
#import <React/RCTModalHostViewManager.h>
54
#import <UIKit/UIKit.h>
65

76
typedef void (^RNNTransitionCompletionBlock)(void);
@@ -13,7 +12,6 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *_Nonnull code, NSString *_
1312

1413
- (instancetype _Nonnull)initWithBridge:(RCTBridge *_Nonnull)bridge
1514
eventHandler:(RNNModalManagerEventHandler *_Nonnull)eventHandler;
16-
- (void)connectModalHostViewManager:(RCTModalHostViewManager *_Nonnull)modalHostViewManager;
1715

1816
- (void)showModal:(UIViewController *_Nonnull)viewController
1917
animated:(BOOL)animated
@@ -25,4 +23,6 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *_Nonnull code, NSString *_
2523

2624
- (void)reset;
2725

26+
- (UIViewController *)topPresentedVC;
27+
2828
@end

‎lib/ios/RNNModalManager.m

-26
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
3232
return self;
3333
}
3434

35-
- (void)connectModalHostViewManager:(RCTModalHostViewManager *)modalHostViewManager {
36-
modalHostViewManager.presentationBlock =
37-
^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated,
38-
dispatch_block_t completionBlock) {
39-
if (reactViewController.presentedViewController != viewController) {
40-
[self showModal:viewController
41-
animated:animated
42-
completion:^(NSString *_Nonnull componentId) {
43-
if (completionBlock)
44-
completionBlock();
45-
}];
46-
}
47-
};
48-
49-
modalHostViewManager.dismissalBlock =
50-
^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated,
51-
dispatch_block_t completionBlock) {
52-
[self dismissModal:viewController
53-
animated:animated
54-
completion:^{
55-
if (completionBlock)
56-
completionBlock();
57-
}];
58-
};
59-
}
60-
6135
- (void)showModal:(UIViewController<RNNLayoutProtocol> *)viewController
6236
animated:(BOOL)animated
6337
completion:(RNNTransitionWithComponentIdCompletionBlock)completion {

‎lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
4534E72620CB6724009F8185 /* RNNLargeTitleOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4534E72420CB6724009F8185 /* RNNLargeTitleOptions.m */; };
5252
500623A525B7003A0086AB39 /* RNNShadowOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 500623A325B7003A0086AB39 /* RNNShadowOptions.h */; };
5353
500623A625B7003A0086AB39 /* RNNShadowOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 500623A425B7003A0086AB39 /* RNNShadowOptions.m */; };
54+
5006E12C27974B8900D106A6 /* RNNModalHostViewManagerHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 5006E12A27974B8900D106A6 /* RNNModalHostViewManagerHandler.h */; };
55+
5006E12D27974B8900D106A6 /* RNNModalHostViewManagerHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5006E12B27974B8900D106A6 /* RNNModalHostViewManagerHandler.m */; };
5456
5008641223856A2D00A55BE9 /* UITabBar+utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5008641023856A2C00A55BE9 /* UITabBar+utils.m */; };
5557
501214C9217741A000435148 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 501214C8217741A000435148 /* libOCMock.a */; };
5658
501223D72173590F000F5F98 /* RNNStackPresenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 501223D52173590F000F5F98 /* RNNStackPresenter.h */; };
@@ -580,6 +582,8 @@
580582
4534E72420CB6724009F8185 /* RNNLargeTitleOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNLargeTitleOptions.m; sourceTree = "<group>"; };
581583
500623A325B7003A0086AB39 /* RNNShadowOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNShadowOptions.h; sourceTree = "<group>"; };
582584
500623A425B7003A0086AB39 /* RNNShadowOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNShadowOptions.m; sourceTree = "<group>"; };
585+
5006E12A27974B8900D106A6 /* RNNModalHostViewManagerHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNModalHostViewManagerHandler.h; sourceTree = "<group>"; };
586+
5006E12B27974B8900D106A6 /* RNNModalHostViewManagerHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNModalHostViewManagerHandler.m; sourceTree = "<group>"; };
583587
5008641023856A2C00A55BE9 /* UITabBar+utils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UITabBar+utils.m"; sourceTree = "<group>"; };
584588
5008641123856A2D00A55BE9 /* UITabBar+utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UITabBar+utils.h"; sourceTree = "<group>"; };
585589
501214C8217741A000435148 /* libOCMock.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libOCMock.a; sourceTree = "<group>"; };
@@ -1092,6 +1096,8 @@
10921096
390AD476200F499D00A8250D /* RNNSwizzles.m */,
10931097
506A2B1220973DFD00F43A95 /* RNNErrorHandler.h */,
10941098
506A2B1320973DFD00F43A95 /* RNNErrorHandler.m */,
1099+
5006E12A27974B8900D106A6 /* RNNModalHostViewManagerHandler.h */,
1100+
5006E12B27974B8900D106A6 /* RNNModalHostViewManagerHandler.m */,
10951101
50644A1E20E11A720026709C /* Constants.h */,
10961102
50644A1F20E11A720026709C /* Constants.m */,
10971103
50706E6B20CE7CA5003345C3 /* UIImage+utils.h */,
@@ -1818,6 +1824,7 @@
18181824
files = (
18191825
506BF6622600AE7600A22755 /* BoundsTransition.h in Headers */,
18201826
91CB34C9250ED50C000C132B /* RNNSearchBarOptions.h in Headers */,
1827+
5006E12C27974B8900D106A6 /* RNNModalHostViewManagerHandler.h in Headers */,
18211828
5060DE73219DAD7E00D0C052 /* ReactNativeNavigation.h in Headers */,
18221829
506BF7CE26067B0500A22755 /* AnimatedUIImageView.h in Headers */,
18231830
5022EDBD2405237100852BA6 /* BottomTabPresenterCreator.h in Headers */,
@@ -2337,6 +2344,7 @@
23372344
5017D9EF239D2FAF00B74047 /* BottomTabsAfterInitialTabAttacher.m in Sources */,
23382345
5008641223856A2D00A55BE9 /* UITabBar+utils.m in Sources */,
23392346
9FDA2ABE24F2A42C005678CC /* RCTConvert+UIFontWeight.m in Sources */,
2347+
5006E12D27974B8900D106A6 /* RNNModalHostViewManagerHandler.m in Sources */,
23402348
9FDA2AC024F2A43B005678CC /* RCTConvert+SideMenuOpenGestureMode.m in Sources */,
23412349
50BCB27223F1650800D6C8E5 /* SharedElementTransition.m in Sources */,
23422350
E5F6C3A822DB4D0F0093C2CE /* UIView+Utils.m in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#import "RNNModalHostViewManagerHandler.h"
2+
#import <OCMock/OCMock.h>
3+
#import <XCTest/XCTest.h>
4+
5+
@interface RNNModalHostViewManagerHandlerTest : XCTestCase
6+
@end
7+
8+
@implementation RNNModalHostViewManagerHandlerTest {
9+
RNNModalHostViewManagerHandler *_uut;
10+
RNNModalManager *_modalManager;
11+
RCTModalHostViewManager *_modalHostViewManager;
12+
}
13+
14+
- (void)setUp {
15+
_modalManager = [OCMockObject partialMockForObject:[[RNNModalManager alloc] init]];
16+
_uut = [[RNNModalHostViewManagerHandler alloc] initWithModalManager:_modalManager];
17+
_modalHostViewManager = [RCTModalHostViewManager new];
18+
[_uut connectModalHostViewManager:_modalHostViewManager];
19+
}
20+
21+
- (void)testPresentationBlock_shouldNotPresentModalTwice {
22+
[[(id)_modalManager reject] showModal:OCMArg.any animated:NO completion:OCMArg.any];
23+
_modalHostViewManager.presentationBlock(nil, _modalManager.topPresentedVC, NO, nil);
24+
}
25+
26+
- (void)testPresentationBlock_shouldShowModal {
27+
UIViewController *vc = UIViewController.new;
28+
_modalHostViewManager.presentationBlock(nil, vc, NO, nil);
29+
XCTAssertEqual(vc, _modalManager.topPresentedVC);
30+
}
31+
32+
- (void)testPresentationBlock_shouldShowAndDismissModal {
33+
XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
34+
UIViewController *vc = UIViewController.new;
35+
_modalHostViewManager.presentationBlock(nil, vc, NO, nil);
36+
XCTAssertEqual(vc, _modalManager.topPresentedVC);
37+
_modalHostViewManager.dismissalBlock(nil, vc, NO, ^{
38+
XCTAssertNotEqual(vc, self->_modalManager.topPresentedVC);
39+
[expectation fulfill];
40+
});
41+
[self waitForExpectationsWithTimeout:5 handler:nil];
42+
}
43+
44+
@end

‎playground/ios/NavigationTests/RNNModalManagerTest.m

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#import "RNNComponentViewController.h"
33
#import "RNNStackController.h"
44
#import <OCMock/OCMock.h>
5+
#import <React/RCTModalHostViewManager.h>
56
#import <XCTest/XCTest.h>
67

78
@interface MockViewController : UIViewController

‎playground/ios/playground.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
1111
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
1212
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
13+
5006E16627974DEA00D106A6 /* RNNModalHostViewManagerHandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5006E16527974DEA00D106A6 /* RNNModalHostViewManagerHandlerTest.m */; };
1314
5007B4312472CA390002AA4E /* RNNNativeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5007B4302472CA390002AA4E /* RNNNativeViewController.m */; };
1415
5007B4342472CBD40002AA4E /* RNNCustomViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5007B4322472CBD30002AA4E /* RNNCustomViewController.m */; };
1516
5007B4352472D3C90002AA4E /* RNNNativeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5007B4302472CA390002AA4E /* RNNNativeViewController.m */; };
@@ -117,6 +118,7 @@
117118
4259AF43A23D928FE78B4A3A /* Pods-NavigationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NavigationTests.debug.xcconfig"; path = "Target Support Files/Pods-NavigationTests/Pods-NavigationTests.debug.xcconfig"; sourceTree = "<group>"; };
118119
4AE37ACF6BFBAB211EE8E7E9 /* Pods-NavigationIOS12Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NavigationIOS12Tests.release.xcconfig"; path = "Target Support Files/Pods-NavigationIOS12Tests/Pods-NavigationIOS12Tests.release.xcconfig"; sourceTree = "<group>"; };
119120
4C14E49C47AA48BEDE90A218 /* Pods-SnapshotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnapshotTests.debug.xcconfig"; path = "Target Support Files/Pods-SnapshotTests/Pods-SnapshotTests.debug.xcconfig"; sourceTree = "<group>"; };
121+
5006E16527974DEA00D106A6 /* RNNModalHostViewManagerHandlerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNModalHostViewManagerHandlerTest.m; sourceTree = "<group>"; };
120122
5007B42F2472CA390002AA4E /* RNNNativeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNNativeViewController.h; sourceTree = "<group>"; };
121123
5007B4302472CA390002AA4E /* RNNNativeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNNativeViewController.m; sourceTree = "<group>"; };
122124
5007B4322472CBD30002AA4E /* RNNCustomViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomViewController.m; sourceTree = "<group>"; };
@@ -403,6 +405,7 @@
403405
E58D26282385888B003F36BA /* RNNFontAttributesCreatorTest.m */,
404406
E58D262A2385888B003F36BA /* RNNLayoutManagerTest.m */,
405407
E58D26432385888C003F36BA /* RNNModalManagerTest.m */,
408+
5006E16527974DEA00D106A6 /* RNNModalHostViewManagerHandlerTest.m */,
406409
503F775B24D19D96005E596D /* RNNModalManagerEventHandlerTest.m */,
407410
E58D263F2385888C003F36BA /* RNNStackControllerTest.m */,
408411
E58D26322385888B003F36BA /* RNNNavigationOptionsTest.m */,
@@ -865,6 +868,7 @@
865868
E58D265F2385888C003F36BA /* RNNBasePresenterTest.m in Sources */,
866869
E58D26542385888C003F36BA /* RNNSideMenuControllerTest.m in Sources */,
867870
E58D264A2385888C003F36BA /* BottomTabsControllerTest.m in Sources */,
871+
5006E16627974DEA00D106A6 /* RNNModalHostViewManagerHandlerTest.m in Sources */,
868872
E58D26472385888C003F36BA /* RNNRootViewControllerTest.m in Sources */,
869873
50C085E8259141E200B0502C /* RNNButtonsPresenterTest.m in Sources */,
870874
E58D26582385888C003F36BA /* UITabBarController+RNNOptionsTest.m in Sources */,

0 commit comments

Comments
 (0)
Failed to load comments.