Skip to content

Commit

Permalink
[Init] Add an executor provider to RCTBridge's init method
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ide committed Apr 14, 2015
1 parent c412585 commit 314d487
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 37 deletions.
25 changes: 20 additions & 5 deletions React/Base/RCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@

@class RCTBridge;
@class RCTEventDispatcher;
@protocol RCTJavaScriptExecutorSource;

/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
* The bridge will call this block to instatiate the modules, and will
* The bridge will call this block to instantiate the modules, and will
* be responsible for invalidating/releasing them when the bridge is destroyed.
* For this reason, the block should always return new module instances, and
* module instances should not be shared between bridges.
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);

/**
* A block that provides a new instance of an RCTJavaScript executor. The bridge
* will call this block to define the executor it uses.
*/
typedef id<RCTJavaScriptExecutor>(^RCTJavaScriptExecutorProviderBlock)(void);

/**
* This function returns the module name for a given class.
*/
Expand All @@ -40,13 +47,18 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. Modules will be automatically
* instantiated using the default contructor, but you can optionally pass in an
* instantiated using the default constructor, but you can optionally pass in an
* array of pre-initialized module instances if they require additional init
* parameters or configuration.
*/
- (instancetype)initWithBundlePath:(NSString *)bundlepath
- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
launchOptions:(NSDictionary *)launchOptions
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions;

/**
* This method is used to call functions in the JavaScript application context.
Expand Down Expand Up @@ -77,7 +89,10 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete;

@property (nonatomic, strong) Class executorClass;
/**
* The source of JavaScript executors used by this bridge.
*/
@property (nonatomic, strong, readonly) id<RCTJavaScriptExecutorSource> executorSource;

/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
Expand Down
36 changes: 23 additions & 13 deletions React/Base/RCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>

#import "RCTContextExecutor.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptLoader.h"
Expand All @@ -25,6 +24,7 @@
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h"
#import "RCTStandardExecutorSource.h"
#import "RCTUtils.h"

/**
Expand Down Expand Up @@ -214,8 +214,6 @@ @implementation RCTModuleMethod
NSString *_methodName;
}

static Class _globalExecutorClass;

NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) {
Expand Down Expand Up @@ -661,12 +659,13 @@ - (NSString *)description
return localModules;
}

#pragma mark - RCTBridge

@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSString *_bundlePath;
NSDictionary *_launchOptions;
RCTBridgeModuleProviderBlock _moduleProvider;
Expand All @@ -678,21 +677,33 @@ @implementation RCTBridge
- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource
{
if ((self = [super init])) {
_bundlePath = bundlePath;
_moduleProvider = block;
_launchOptions = launchOptions;
_executorSource = executorSource;
[self setUp];
[self bindKeys];
}

return self;
}

- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
return [self initWithBundlePath:bundlePath
moduleProvider:block
launchOptions:launchOptions
executorSource:[[RCTStandardExecutorSource alloc] init]];
}

- (void)setUp
{
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = [[executorClass alloc] init];
_javaScriptExecutor = [_executorSource executor];
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
Expand Down Expand Up @@ -801,23 +812,22 @@ - (void)bindKeys
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
RCTBridge *strongSelf = weakSelf;
if (!strongSelf) {
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
return;
}
strongSelf->_executorClass = Nil;
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
source.executorType = RCTStandardExecutorTypeJSContext;
[strongSelf reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
RCTBridge *strongSelf = weakSelf;
if (!strongSelf) {
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
return;
}
strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf->_executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
source.executorType = RCTStandardExecutorTypeWebSocket;
[strongSelf reload];
}];
#endif
Expand Down
40 changes: 27 additions & 13 deletions React/Base/RCTDevMenu.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

#import "RCTDevMenu.h"

#import "RCTBridge.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTJavaScriptExecutorSource.h"
#import "RCTStandardExecutorSource.h"
#import "RCTSourceCode.h"
#import "RCTWebViewExecutor.h"

@interface RCTDevMenu () <UIActionSheetDelegate> {
BOOL _liveReload;
Expand All @@ -34,14 +35,20 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge

- (void)show
{
NSString *debugTitleChrome = self.bridge.executorClass != Nil && self.bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = self.bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *debugTitleChrome = nil;
NSString *debugTitleSafari = nil;
if ([_bridge.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
RCTStandardExecutorType executorType = source.executorType;
debugTitleChrome = (executorType == RCTStandardExecutorTypeWebSocket) ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
debugTitleSafari = (executorType == RCTStandardExecutorTypeUIWebView) ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
}
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil];
otherButtonTitles:@"Reload", liveReloadTitle, debugTitleChrome, debugTitleSafari, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
}
Expand All @@ -51,17 +58,24 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
if (buttonIndex == 0) {
[self.bridge reload];
} else if (buttonIndex == 1) {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : nil;
[self.bridge reload];
} else if (buttonIndex == 2) {
Class cls = [RCTWebViewExecutor class];
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : Nil;
[self.bridge reload];
} else if (buttonIndex == 3) {
_liveReload = !_liveReload;
[self _pollAndReload];
} else if (buttonIndex == 2) {
[self _toggleExecutorType:RCTStandardExecutorTypeWebSocket];
} else if (buttonIndex == 3) {
[self _toggleExecutorType:RCTStandardExecutorTypeUIWebView];
}
}

- (void)_toggleExecutorType:(RCTStandardExecutorType)executorType
{
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)_bridge.executorSource;
if (source.executorType == executorType) {
source.executorType = RCTStandardExecutorTypeJSContext;
} else {
source.executorType = executorType;
}
[_bridge reload];
}

- (void)_pollAndReload
Expand Down
21 changes: 21 additions & 0 deletions React/Base/RCTJavaScriptExecutorSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

@protocol RCTJavaScriptExecutor;

@protocol RCTJavaScriptExecutorSource <NSObject>

/**
* Return a new JavaScript executor to run a React application.
*/
- (id<RCTJavaScriptExecutor>)executor;

@end
10 changes: 6 additions & 4 deletions React/Base/RCTRootView.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#import "RCTBridge.h"

@class RCTStandardExecutorSource;

extern NSString *const RCTJavaScriptDidLoadNotification;
extern NSString *const RCTReloadNotification;
extern NSString *const RCTReloadViewsNotification;
Expand Down Expand Up @@ -55,11 +57,11 @@ extern NSString *const RCTReloadViewsNotification;
@property (nonatomic, copy) NSDictionary *initialProperties;

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

/**
* If YES will watch for shake gestures and show development menu
Expand Down
2 changes: 0 additions & 2 deletions React/Base/RCTRootView.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#import <objc/runtime.h>

#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTDevMenu.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
Expand All @@ -21,7 +20,6 @@
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"

NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
Expand Down
26 changes: 26 additions & 0 deletions React/Base/RCTStandardExecutorSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

#import "RCTJavaScriptExecutorSource.h"

typedef NS_ENUM(NSUInteger, RCTStandardExecutorType) {
RCTStandardExecutorTypeJSContext,
RCTStandardExecutorTypeUIWebView,
RCTStandardExecutorTypeWebSocket,
};

@interface RCTStandardExecutorSource : NSObject <RCTJavaScriptExecutorSource>

@property (nonatomic) RCTStandardExecutorType executorType;

- (id<RCTJavaScriptExecutor>)executor;

@end
38 changes: 38 additions & 0 deletions React/Base/RCTStandardExecutorSource.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2015-present Facebook. All rights reserved.

#import "RCTStandardExecutorSource.h"

#import "RCTContextExecutor.h"
#import "RCTJavaScriptExecutor.h"
#import "RCTLog.h"
#import "RCTWebViewExecutor.h"

@implementation RCTStandardExecutorSource

- (instancetype)init
{
if (self = [super init]) {
_executorType = RCTStandardExecutorTypeJSContext;
}
return self;
}

- (id<RCTJavaScriptExecutor>)executor
{
switch (_executorType) {
case RCTStandardExecutorTypeJSContext:
return [[RCTContextExecutor alloc] init];
case RCTStandardExecutorTypeUIWebView:
return [[RCTWebViewExecutor alloc]initWithWebView:[[UIWebView alloc] init]];
case RCTStandardExecutorTypeWebSocket: {
Class executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
executorClass = [RCTContextExecutor class];
}
return [[executorClass alloc] init];
}
}
}

@end
Loading

0 comments on commit 314d487

Please sign in to comment.