Skip to content

Commit e6f1e9e

Browse files
committed
[Init] Add an executor provider to RCTBridge's init method
If you construct an RCTBridge you may want to configure the executor. However the constructor synchronously calls `setUp` and sets up the executor. Instead, let the code that constructs the bridge specify an executor source, which controls how the executor is provided. This allows for customizing the web view executor with a UIWebView of your choice, or configuring the URL of the debugger proxy that the web socket executor connects to. Now that RCTRootView takes a bridge in one of its initializers, it is possible to create a bridge with a custom executor and then use that to set up a root view as well. Test Plan: Run the UIExplorer app and confirm I am able to connect to the plain JSContext via Safari. Hit Cmd-D and see Chrome open a Tab. Hit Cmd-N and see that I can connect to a plain JSContext again. Shake the simulator and select "Enable Safari Debugging" and see that I can connect to the UIWebView from Safari. tl;dr the keyboard shortcuts and dev menu work as expected. Fixes #288
1 parent 290be64 commit e6f1e9e

9 files changed

Lines changed: 176 additions & 43 deletions

React/Base/RCTBridge.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
@class RCTBridge;
1717
@class RCTEventDispatcher;
18+
@protocol RCTJavaScriptExecutorSource;
1819

1920
/**
2021
* This notification triggers a reload of all bridges currently running.
@@ -29,13 +30,19 @@ extern NSString *const RCTJavaScriptDidLoadNotification;
2930
/**
3031
* This block can be used to instantiate modules that require additional
3132
* init parameters, or additional configuration prior to being used.
32-
* The bridge will call this block to instatiate the modules, and will
33+
* The bridge will call this block to instantiate the modules, and will
3334
* be responsible for invalidating/releasing them when the bridge is destroyed.
3435
* For this reason, the block should always return new module instances, and
3536
* module instances should not be shared between bridges.
3637
*/
3738
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
3839

40+
/**
41+
* A block that provides a new instance of an RCTJavaScript executor. The bridge
42+
* will call this block to define the executor it uses.
43+
*/
44+
typedef id<RCTJavaScriptExecutor>(^RCTJavaScriptExecutorProviderBlock)(void);
45+
3946
/**
4047
* This function returns the module name for a given class.
4148
*/
@@ -50,13 +57,18 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
5057
* The designated initializer. This creates a new bridge on top of the specified
5158
* executor. The bridge should then be used for all subsequent communication
5259
* with the JavaScript code running in the executor. Modules will be automatically
53-
* instantiated using the default contructor, but you can optionally pass in an
60+
* instantiated using the default constructor, but you can optionally pass in an
5461
* array of pre-initialized module instances if they require additional init
5562
* parameters or configuration.
5663
*/
5764
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
5865
moduleProvider:(RCTBridgeModuleProviderBlock)block
59-
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
66+
launchOptions:(NSDictionary *)launchOptions
67+
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource NS_DESIGNATED_INITIALIZER;
68+
69+
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
70+
moduleProvider:(RCTBridgeModuleProviderBlock)block
71+
launchOptions:(NSDictionary *)launchOptions;
6072

6173
/**
6274
* This method is used to call functions in the JavaScript application context.
@@ -87,7 +99,10 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
8799
url:(NSURL *)url
88100
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
89101

90-
@property (nonatomic, strong) Class executorClass;
102+
/**
103+
* The source of JavaScript executors used by this bridge.
104+
*/
105+
@property (nonatomic, strong, readonly) id<RCTJavaScriptExecutorSource> executorSource;
91106

92107
/**
93108
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a

React/Base/RCTBridge.m

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#import <mach-o/dyld.h>
1717
#import <mach-o/getsect.h>
1818

19-
#import "RCTContextExecutor.h"
2019
#import "RCTConvert.h"
2120
#import "RCTEventDispatcher.h"
2221
#import "RCTJavaScriptLoader.h"
@@ -25,6 +24,7 @@
2524
#import "RCTRedBox.h"
2625
#import "RCTRootView.h"
2726
#import "RCTSparseArray.h"
27+
#import "RCTStandardExecutorSource.h"
2828
#import "RCTUtils.h"
2929

3030
NSString *const RCTReloadNotification = @"RCTReloadNotification";
@@ -217,8 +217,6 @@ @implementation RCTModuleMethod
217217
NSString *_methodName;
218218
}
219219

220-
static Class _globalExecutorClass;
221-
222220
NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
223221
NSRange colonRange = [methodName rangeOfString:@":"];
224222
if (colonRange.length) {
@@ -664,12 +662,13 @@ - (NSString *)description
664662
return localModules;
665663
}
666664

665+
#pragma mark - RCTBridge
666+
667667
@implementation RCTBridge
668668
{
669669
RCTSparseArray *_modulesByID;
670670
NSDictionary *_modulesByName;
671671
id<RCTJavaScriptExecutor> _javaScriptExecutor;
672-
Class _executorClass;
673672
NSURL *_bundleURL;
674673
RCTBridgeModuleProviderBlock _moduleProvider;
675674
BOOL _loading;
@@ -680,21 +679,33 @@ @implementation RCTBridge
680679
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
681680
moduleProvider:(RCTBridgeModuleProviderBlock)block
682681
launchOptions:(NSDictionary *)launchOptions
682+
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource
683683
{
684684
if ((self = [super init])) {
685685
_bundleURL = bundleURL;
686686
_moduleProvider = block;
687687
_launchOptions = [launchOptions copy];
688+
_executorSource = executorSource;
688689
[self setUp];
689690
[self bindKeys];
690691
}
691692

692693
return self;
693694
}
695+
696+
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
697+
moduleProvider:(RCTBridgeModuleProviderBlock)block
698+
launchOptions:(NSDictionary *)launchOptions
699+
{
700+
return [self initWithBundleURL:bundleURL
701+
moduleProvider:block
702+
launchOptions:launchOptions
703+
executorSource:[[RCTStandardExecutorSource alloc] init]];
704+
}
705+
694706
- (void)setUp
695707
{
696-
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
697-
_javaScriptExecutor = [[executorClass alloc] init];
708+
_javaScriptExecutor = [_executorSource executor];
698709
_latestJSExecutor = _javaScriptExecutor;
699710
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
700711
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
@@ -803,34 +814,34 @@ - (void)bindKeys
803814
// will work like a charm!
804815
[commands registerKeyCommandWithInput:@""
805816
modifierFlags:UIKeyModifierCommand
806-
action:NULL];
807-
// reload in current mode
817+
action:^(UIKeyCommand *command) {
818+
// Do nothing
819+
}];
808820
[commands registerKeyCommandWithInput:@"r"
809821
modifierFlags:UIKeyModifierCommand
810822
action:^(UIKeyCommand *command) {
811823
[weakSelf reload];
812824
}];
813-
// reset to normal mode
814825
[commands registerKeyCommandWithInput:@"n"
815826
modifierFlags:UIKeyModifierCommand
816827
action:^(UIKeyCommand *command) {
817-
__strong RCTBridge *strongSelf = weakSelf;
818-
strongSelf.executorClass = Nil;
828+
RCTBridge *strongSelf = weakSelf;
829+
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
830+
return;
831+
}
832+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
833+
source.executorType = RCTStandardExecutorTypeJSContext;
819834
[strongSelf reload];
820835
}];
821-
// reload in debug mode
822836
[commands registerKeyCommandWithInput:@"d"
823837
modifierFlags:UIKeyModifierCommand
824838
action:^(UIKeyCommand *command) {
825-
__strong RCTBridge *strongSelf = weakSelf;
826-
strongSelf.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
827-
if (!strongSelf.executorClass) {
828-
strongSelf.executorClass = NSClassFromString(@"RCTWebViewExecutor");
829-
}
830-
if (!strongSelf.executorClass) {
831-
RCTLogError(@"WebSocket debugger is not available. "
832-
"Did you forget to include RCTWebSocketExecutor?");
839+
RCTBridge *strongSelf = weakSelf;
840+
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
841+
return;
833842
}
843+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
844+
source.executorType = RCTStandardExecutorTypeWebSocket;
834845
[strongSelf reload];
835846
}];
836847
#endif

React/Base/RCTDevMenu.m

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
#import "RCTDevMenu.h"
1111

12+
#import "RCTBridge.h"
1213
#import "RCTRedBox.h"
13-
#import "RCTRootView.h"
14+
#import "RCTJavaScriptExecutorSource.h"
15+
#import "RCTStandardExecutorSource.h"
1416
#import "RCTSourceCode.h"
15-
#import "RCTWebViewExecutor.h"
1617

1718
@interface RCTDevMenu () <UIActionSheetDelegate>
1819

@@ -34,14 +35,20 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
3435

3536
- (void)show
3637
{
37-
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
38-
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
38+
NSString *debugTitleChrome = nil;
39+
NSString *debugTitleSafari = nil;
40+
if ([_bridge.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
41+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
42+
RCTStandardExecutorType executorType = source.executorType;
43+
debugTitleChrome = (executorType == RCTStandardExecutorTypeWebSocket) ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
44+
debugTitleSafari = (executorType == RCTStandardExecutorTypeUIWebView) ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
45+
}
3946
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
4047
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
4148
delegate:self
4249
cancelButtonTitle:@"Cancel"
4350
destructiveButtonTitle:nil
44-
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil];
51+
otherButtonTitles:@"Reload", liveReloadTitle, debugTitleChrome, debugTitleSafari, nil];
4552
actionSheet.actionSheetStyle = UIBarStyleBlack;
4653
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
4754
}
@@ -51,17 +58,24 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
5158
if (buttonIndex == 0) {
5259
[_bridge reload];
5360
} else if (buttonIndex == 1) {
54-
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
55-
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
56-
[_bridge reload];
57-
} else if (buttonIndex == 2) {
58-
Class cls = [RCTWebViewExecutor class];
59-
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
60-
[_bridge reload];
61-
} else if (buttonIndex == 3) {
6261
_liveReload = !_liveReload;
6362
[self _pollAndReload];
63+
} else if (buttonIndex == 2) {
64+
[self _toggleExecutorType:RCTStandardExecutorTypeWebSocket];
65+
} else if (buttonIndex == 3) {
66+
[self _toggleExecutorType:RCTStandardExecutorTypeUIWebView];
67+
}
68+
}
69+
70+
- (void)_toggleExecutorType:(RCTStandardExecutorType)executorType
71+
{
72+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
73+
if (source.executorType == executorType) {
74+
source.executorType = RCTStandardExecutorTypeJSContext;
75+
} else {
76+
source.executorType = executorType;
6477
}
78+
[_bridge reload];
6579
}
6680

6781
- (void)_pollAndReload
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <Foundation/Foundation.h>
11+
12+
@protocol RCTJavaScriptExecutor;
13+
14+
@protocol RCTJavaScriptExecutorSource <NSObject>
15+
16+
/**
17+
* Return a new JavaScript executor to run a React application.
18+
*/
19+
- (id<RCTJavaScriptExecutor>)executor;
20+
21+
@end

React/Base/RCTRootView.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#import "RCTBridge.h"
1313

14+
@class RCTStandardExecutorSource;
15+
1416
@interface RCTRootView : UIView <RCTInvalidating>
1517

1618
/**
@@ -51,11 +53,11 @@
5153
@property (nonatomic, copy) NSDictionary *initialProperties;
5254

5355
/**
54-
* The class of the RCTJavaScriptExecutor to use with this view.
55-
* If not specified, it will default to using RCTContextExecutor.
56-
* Changes will take effect next time the bundle is reloaded.
56+
* The source of the JavaScript executors that this RCTRootView uses. Set the
57+
* executor type through the provider and reload the RCTRootView for a new
58+
* executor.
5759
*/
58-
@property (nonatomic, strong) Class executorClass;
60+
@property (nonatomic, strong, readonly) RCTStandardExecutorSource *executorSource;
5961

6062
/**
6163
* If YES will watch for shake gestures and show development menu

React/Base/RCTRootView.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#import <objc/runtime.h>
1313

1414
#import "RCTBridge.h"
15-
#import "RCTContextExecutor.h"
1615
#import "RCTDevMenu.h"
1716
#import "RCTEventDispatcher.h"
1817
#import "RCTKeyCommands.h"
@@ -21,7 +20,6 @@
2120
#import "RCTTouchHandler.h"
2221
#import "RCTUIManager.h"
2322
#import "RCTUtils.h"
24-
#import "RCTWebViewExecutor.h"
2523
#import "UIView+React.h"
2624

2725
/**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <Foundation/Foundation.h>
11+
12+
#import "RCTJavaScriptExecutorSource.h"
13+
14+
typedef NS_ENUM(NSUInteger, RCTStandardExecutorType) {
15+
RCTStandardExecutorTypeJSContext,
16+
RCTStandardExecutorTypeUIWebView,
17+
RCTStandardExecutorTypeWebSocket,
18+
};
19+
20+
@interface RCTStandardExecutorSource : NSObject <RCTJavaScriptExecutorSource>
21+
22+
@property (nonatomic) RCTStandardExecutorType executorType;
23+
24+
- (id<RCTJavaScriptExecutor>)executor;
25+
26+
@end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2015-present Facebook. All rights reserved.
2+
3+
#import "RCTStandardExecutorSource.h"
4+
5+
#import "RCTContextExecutor.h"
6+
#import "RCTJavaScriptExecutor.h"
7+
#import "RCTLog.h"
8+
#import "RCTWebViewExecutor.h"
9+
10+
@implementation RCTStandardExecutorSource
11+
12+
- (instancetype)init
13+
{
14+
if (self = [super init]) {
15+
_executorType = RCTStandardExecutorTypeJSContext;
16+
}
17+
return self;
18+
}
19+
20+
- (id<RCTJavaScriptExecutor>)executor
21+
{
22+
switch (_executorType) {
23+
case RCTStandardExecutorTypeJSContext:
24+
return [[RCTContextExecutor alloc] init];
25+
case RCTStandardExecutorTypeUIWebView:
26+
return [[RCTWebViewExecutor alloc]initWithWebView:[[UIWebView alloc] init]];
27+
case RCTStandardExecutorTypeWebSocket: {
28+
Class executorClass = NSClassFromString(@"RCTWebSocketExecutor");
29+
if (!executorClass) {
30+
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
31+
executorClass = [RCTContextExecutor class];
32+
}
33+
return [[executorClass alloc] init];
34+
}
35+
}
36+
}
37+
38+
@end

0 commit comments

Comments
 (0)