Skip to content

Commit 66fedc6

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 initializes 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, pick Chrome 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 1efd2c8 commit 66fedc6

10 files changed

Lines changed: 197 additions & 92 deletions

React/Base/RCTBridge.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
@class RCTBridge;
1919
@class RCTEventDispatcher;
20+
@protocol RCTJavaScriptExecutorSource;
2021

2122
/**
2223
* This notification triggers a reload of all bridges currently running.
@@ -36,7 +37,7 @@ extern NSString *const RCTJavaScriptDidFailToLoadNotification;
3637
/**
3738
* This block can be used to instantiate modules that require additional
3839
* init parameters, or additional configuration prior to being used.
39-
* The bridge will call this block to instatiate the modules, and will
40+
* The bridge will call this block to instantiate the modules, and will
4041
* be responsible for invalidating/releasing them when the bridge is destroyed.
4142
* For this reason, the block should always return new module instances, and
4243
* module instances should not be shared between bridges.
@@ -57,13 +58,18 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
5758
* The designated initializer. This creates a new bridge on top of the specified
5859
* executor. The bridge should then be used for all subsequent communication
5960
* with the JavaScript code running in the executor. Modules will be automatically
60-
* instantiated using the default contructor, but you can optionally pass in an
61+
* instantiated using the default constructor, but you can optionally pass in an
6162
* array of pre-initialized module instances if they require additional init
6263
* parameters or configuration.
6364
*/
6465
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
6566
moduleProvider:(RCTBridgeModuleProviderBlock)block
66-
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
67+
launchOptions:(NSDictionary *)launchOptions
68+
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource NS_DESIGNATED_INITIALIZER;
69+
70+
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
71+
moduleProvider:(RCTBridgeModuleProviderBlock)block
72+
launchOptions:(NSDictionary *)launchOptions;
6773

6874
/**
6975
* This method is used to call functions in the JavaScript application context.
@@ -90,7 +96,10 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
9096
*/
9197
@property (nonatomic, copy) NSURL *bundleURL;
9298

93-
@property (nonatomic, strong) Class executorClass;
99+
/**
100+
* The source of JavaScript executors used by this bridge.
101+
*/
102+
@property (nonatomic, strong, readonly) id<RCTJavaScriptExecutorSource> executorSource;
94103

95104
/**
96105
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a

React/Base/RCTBridge.m

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#import "RCTRootView.h"
2828
#import "RCTSourceCode.h"
2929
#import "RCTSparseArray.h"
30+
#import "RCTStandardExecutorSource.h"
3031
#import "RCTUtils.h"
3132

3233
NSString *const RCTReloadNotification = @"RCTReloadNotification";
@@ -764,6 +765,17 @@ @implementation RCTBridge
764765
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
765766
moduleProvider:(RCTBridgeModuleProviderBlock)block
766767
launchOptions:(NSDictionary *)launchOptions
768+
{
769+
return [self initWithBundleURL:bundleURL
770+
moduleProvider:block
771+
launchOptions:launchOptions
772+
executorSource:[[RCTStandardExecutorSource alloc] init]];
773+
}
774+
775+
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
776+
moduleProvider:(RCTBridgeModuleProviderBlock)block
777+
launchOptions:(NSDictionary *)launchOptions
778+
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource
767779
{
768780
RCTAssertMainThread();
769781

@@ -776,6 +788,7 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
776788
_bundleURL = bundleURL;
777789
_moduleProvider = block;
778790
_launchOptions = [launchOptions copy];
791+
_executorSource = executorSource;
779792
[self bindKeys];
780793
[self setUp];
781794
}
@@ -932,8 +945,8 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge
932945
/**
933946
* Initialize executor to allow enqueueing calls
934947
*/
935-
Class executorClass = self.executorClass ?: [RCTContextExecutor class];
936-
_javaScriptExecutor = RCTCreateExecutor(executorClass);
948+
_javaScriptExecutor = [_parentBridge.executorSource executor];
949+
RCTSetNewExecutorID(_javaScriptExecutor);
937950
_latestJSExecutor = _javaScriptExecutor;
938951

939952
/**
@@ -971,16 +984,9 @@ - (void)reload
971984
[_parentBridge reload];
972985
}
973986

974-
- (Class)executorClass
987+
- (id<RCTJavaScriptExecutorSource>)executorSource
975988
{
976-
return _parentBridge.executorClass;
977-
}
978-
979-
- (void)setExecutorClass:(Class)executorClass
980-
{
981-
RCTAssertMainThread();
982-
983-
_parentBridge.executorClass = executorClass;
989+
return _parentBridge.executorSource;
984990
}
985991

986992
- (BOOL)isLoading

React/Base/RCTDevMenu.m

Lines changed: 57 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
#import "RCTLog.h"
1717
#import "RCTPerfStats.h"
1818
#import "RCTProfile.h"
19-
#import "RCTRootView.h"
19+
#import "RCTRedBox.h"
20+
#import "RCTJavaScriptExecutorSource.h"
21+
#import "RCTStandardExecutorSource.h"
2022
#import "RCTSourceCode.h"
2123
#import "RCTUtils.h"
2224

@@ -45,7 +47,7 @@ - (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
4547

4648
@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate>
4749

48-
@property (nonatomic, strong) Class executorClass;
50+
@property (nonatomic, assign) RCTStandardExecutorType executorType;
4951

5052
@end
5153

@@ -114,7 +116,7 @@ - (instancetype)init
114116
[commands registerKeyCommandWithInput:@"n"
115117
modifierFlags:UIKeyModifierCommand
116118
action:^(UIKeyCommand *command) {
117-
weakSelf.executorClass = Nil;
119+
weakSelf.executorType = RCTStandardExecutorTypeDefault;
118120
}];
119121
#endif
120122

@@ -148,7 +150,7 @@ - (void)updateSettings
148150
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
149151
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
150152
self.showFPS = [_settings[@"showFPS"] ?: @NO boolValue];
151-
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
153+
self.executorType = [_settings[@"executorType"] ?: @(RCTStandardExecutorTypeDefault) unsignedIntegerValue];
152154
}
153155

154156
- (void)jsLoaded:(NSNotification *)notification
@@ -177,7 +179,7 @@ - (void)jsLoaded:(NSNotification *)notification
177179
// Hit these setters again after bridge has finished loading
178180
self.profilingEnabled = _profilingEnabled;
179181
self.liveReloadEnabled = _liveReloadEnabled;
180-
self.executorClass = _executorClass;
182+
self.executorType = _executorType;
181183
});
182184
}
183185

@@ -231,16 +233,24 @@ - (void)toggle
231233
return;
232234
}
233235

234-
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
235-
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
236-
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
237-
238236
UIActionSheet *actionSheet =
239237
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
240238
delegate:self
241239
cancelButtonTitle:nil
242240
destructiveButtonTitle:nil
243-
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
241+
otherButtonTitles:@"Reload", nil];
242+
243+
if ([_bridge.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
244+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
245+
RCTStandardExecutorType executorType = source.executorType;
246+
NSString *debugTitleChrome = (executorType == RCTStandardExecutorTypeWebSocket) ? @"Disable Chrome Debugging" : @"Debug in Chrome";
247+
NSString *debugTitleSafari = (executorType == RCTStandardExecutorTypeUIWebView) ? @"Disable Safari Debugging" : @"Debug in Safari";
248+
[actionSheet addButtonWithTitle:debugTitleChrome];
249+
[actionSheet addButtonWithTitle:debugTitleSafari];
250+
}
251+
252+
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
253+
[actionSheet addButtonWithTitle:fpsMonitor];
244254

245255
[actionSheet addButtonWithTitle:@"Inspect Element"];
246256

@@ -275,47 +285,25 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
275285
return;
276286
}
277287

278-
switch (buttonIndex) {
279-
case 0: {
280-
[self reload];
281-
break;
282-
}
283-
case 1: {
284-
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
285-
if (!cls) {
286-
[[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable"
287-
message:@"You need to include the RCTWebSocket library to enable Chrome debugging"
288-
delegate:nil
289-
cancelButtonTitle:@"OK"
290-
otherButtonTitles:nil] show];
291-
return;
292-
}
293-
self.executorClass = (_executorClass == cls) ? Nil : cls;
294-
break;
295-
}
296-
case 2: {
297-
Class cls = NSClassFromString(@"RCTWebViewExecutor");
298-
self.executorClass = (_executorClass == cls) ? Nil : cls;
299-
break;
300-
}
301-
case 3: {
302-
self.showFPS = !_showFPS;
303-
break;
304-
}
305-
case 4: {
306-
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
307-
break;
308-
}
309-
case 5: {
310-
self.liveReloadEnabled = !_liveReloadEnabled;
311-
break;
312-
}
313-
case 6: {
314-
self.profilingEnabled = !_profilingEnabled;
315-
break;
316-
}
317-
default:
318-
break;
288+
if (buttonIndex == 0) {
289+
[self reload];
290+
return;
291+
}
292+
293+
// Note: after supporting iOS 8+, use UIAlertController which has a more cohesive API
294+
NSString *buttonTitle = [actionSheet buttonTitleAtIndex:buttonIndex];
295+
if ([buttonTitle containsString:@"Chrome"]) {
296+
[self _toggleExecutorType:RCTStandardExecutorTypeWebSocket];
297+
} else if ([buttonTitle containsString:@"Safari"]) {
298+
[self _toggleExecutorType:RCTStandardExecutorTypeUIWebView];
299+
} else if ([buttonTitle containsString:@"FPS Monitor"]) {
300+
self.showFPS = !_showFPS;
301+
} else if ([buttonTitle containsString:@"Inspect Element"]) {
302+
[_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
303+
} else if ([buttonTitle containsString:@"Live Reload"]) {
304+
self.liveReloadEnabled = !_liveReloadEnabled;
305+
} else if ([buttonTitle containsString:@"Profiling"]) {
306+
self.profilingEnabled = !_profilingEnabled;
319307
}
320308
}
321309

@@ -358,27 +346,28 @@ - (void)setLiveReloadEnabled:(BOOL)enabled
358346
}
359347
}
360348

361-
- (void)setExecutorClass:(Class)executorClass
349+
- (void)_toggleExecutorType:(RCTStandardExecutorType)executorType
362350
{
363-
if (_executorClass != executorClass) {
364-
_executorClass = executorClass;
365-
[self updateSetting:@"executorClass" value: NSStringFromClass(executorClass)];
351+
if (_executorType == executorType) {
352+
self.executorType = RCTStandardExecutorTypeDefault;
353+
} else {
354+
self.executorType = executorType;
366355
}
356+
}
367357

368-
if (_bridge.executorClass != executorClass) {
369-
370-
// TODO (6929129): we can remove this special case test once we have better
371-
// support for custom executors in the dev menu. But right now this is
372-
// needed to prevent overriding a custom executor with the default if a
373-
// custom executor has been set directly on the bridge
374-
if (executorClass == Nil &&
375-
(_bridge.executorClass != NSClassFromString(@"RCTWebSocketExecutor") &&
376-
_bridge.executorClass != NSClassFromString(@"RCTWebViewExecutor"))) {
377-
return;
378-
}
358+
- (void)setExecutorType:(RCTStandardExecutorType)executorType
359+
{
360+
if (_executorType != executorType) {
361+
_executorType = executorType;
362+
[self updateSetting:@"executorType" value:@(executorType)];
363+
}
379364

380-
_bridge.executorClass = executorClass;
381-
[self reload];
365+
if ([_bridge.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
366+
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
367+
if (source.executorType != executorType) {
368+
source.executorType = executorType;
369+
[_bridge reload];
370+
}
382371
}
383372
}
384373

React/Base/RCTJavaScriptExecutor.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,12 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
6161
@end
6262

6363
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
64-
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
64+
__used static void RCTSetNewExecutorID(id<RCTJavaScriptExecutor> executor)
6565
{
6666
static NSUInteger executorID = 0;
67-
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
6867
if (executor) {
6968
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
7069
}
71-
return executor;
7270
}
7371

7472
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)
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
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
* The backing view controller of the root view.

React/Base/RCTRootView.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
#import "RCTAssert.h"
1515
#import "RCTBridge.h"
16-
#import "RCTContextExecutor.h"
1716
#import "RCTEventDispatcher.h"
1817
#import "RCTKeyCommands.h"
1918
#import "RCTLog.h"
@@ -22,7 +21,6 @@
2221
#import "RCTUIManager.h"
2322
#import "RCTUtils.h"
2423
#import "RCTView.h"
25-
#import "RCTWebViewExecutor.h"
2624
#import "UIView+React.h"
2725

2826
@interface RCTBridge (RCTRootView)

0 commit comments

Comments
 (0)