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 30, 2015
1 parent b99744a commit bede2fb
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 47 deletions.
23 changes: 19 additions & 4 deletions React/Base/RCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

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

/**
* This notification triggers a reload of all bridges currently running.
Expand All @@ -31,13 +32,19 @@ extern NSString *const RCTJavaScriptDidLoadNotification;
/**
* 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 @@ -52,13 +59,18 @@ RCT_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)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
launchOptions:(NSDictionary *)launchOptions
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions;

/**
* This method is used to call functions in the JavaScript application context.
Expand Down Expand Up @@ -94,7 +106,10 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
*/
@property (nonatomic, copy) NSURL *bundleURL;

@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
53 changes: 33 additions & 20 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 @@ -26,6 +25,7 @@
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h"
#import "RCTStandardExecutorSource.h"
#import "RCTUtils.h"

NSString *const RCTReloadNotification = @"RCTReloadNotification";
Expand Down Expand Up @@ -234,8 +234,6 @@ @implementation RCTModuleMethod
dispatch_block_t _methodQueue;
}

static Class _globalExecutorClass;

static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{
NSRange colonRange = [methodName rangeOfString:@":"];
Expand Down Expand Up @@ -795,14 +793,15 @@ - (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink

@end

#pragma mark - RCTBridge

@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
Expand All @@ -818,22 +817,35 @@ @implementation RCTBridge
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
executorSource:(id<RCTJavaScriptExecutorSource>)executorSource
{
if ((self = [super init])) {
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];

_executorSource = executorSource;

[self setUp];
[self bindKeys];
}
return self;
}

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
return [self initWithBundleURL:bundleURL
moduleProvider:block
launchOptions:launchOptions
executorSource:[[RCTStandardExecutorSource alloc] init]];
}

- (void)setUp
{
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_javaScriptExecutor = [_executorSource executor];
RCTSetNewExecutorID(_javaScriptExecutor);

_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
Expand Down Expand Up @@ -973,19 +985,23 @@ - (void)bindKeys
// will work like a charm!
[commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:NULL];
// reload in current mode
action:^(UIKeyCommand *command) {
// Do nothing
}];
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
// reset to normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = Nil;
RCTBridge *strongSelf = weakSelf;
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
return;
}
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
source.executorType = RCTStandardExecutorTypeJSContext;
[strongSelf reload];
}];

Expand All @@ -995,15 +1011,12 @@ - (void)bindKeys
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf.executorClass) {
strongSelf.executorClass = NSClassFromString(@"RCTWebViewExecutor");
}
if (!strongSelf.executorClass) {
RCTLogError(@"WebSocket debugger is not available. "
"Did you forget to include RCTWebSocketExecutor?");
RCTBridge *strongSelf = weakSelf;
if (!strongSelf || ![strongSelf.executorSource isKindOfClass:[RCTStandardExecutorSource class]]) {
return;
}
RCTStandardExecutorSource *source = (RCTStandardExecutorSource *)strongSelf.executorSource;
source.executorType = RCTStandardExecutorTypeWebSocket;
[strongSelf reload];
}];
#endif
Expand Down
41 changes: 27 additions & 14 deletions React/Base/RCTDevMenu.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
#import "RCTRedBox.h"
#import "RCTJavaScriptExecutorSource.h"
#import "RCTStandardExecutorSource.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"

Expand Down Expand Up @@ -90,18 +92,22 @@ - (void)show
return;
}

NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"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 = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";

UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];

otherButtonTitles:@"Reload", liveReloadTitle, profilingTitle, debugTitleChrome, debugTitleSafari, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
}
Expand All @@ -116,30 +122,37 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
break;
}
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 2: {
Class cls = NSClassFromString(@"RCTWebViewExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
self.profilingEnabled = !_profilingEnabled;
break;
}
case 3: {
self.liveReloadEnabled = !_liveReloadEnabled;
[self _toggleExecutorType:RCTStandardExecutorTypeWebSocket];
break;
}
case 4: {
self.profilingEnabled = !_profilingEnabled;
[self _toggleExecutorType:RCTStandardExecutorTypeUIWebView];
break;
}
default:
break;
}
}

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

- (void)setProfilingEnabled:(BOOL)enabled
{
if (_profilingEnabled == enabled) {
Expand Down
4 changes: 1 addition & 3 deletions React/Base/RCTJavaScriptExecutor.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
@end

static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
__used static void RCTSetNewExecutorID(id<RCTJavaScriptExecutor> executor)
{
static NSUInteger executorID = 0;
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
if (executor) {
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
}
return executor;
}

__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)
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;

@interface RCTRootView : UIView <RCTInvalidating>

/**
Expand Down Expand Up @@ -51,11 +53,11 @@
@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;

/**
* The backing view controller of the root view.
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,15 +12,13 @@
#import <objc/runtime.h>

#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTSourceCode.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"

@interface RCTUIManager (RCTRootView)
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
Loading

0 comments on commit bede2fb

Please sign in to comment.