Skip to content

Commit

Permalink
[ReactNative] Unfork RKRootView
Browse files Browse the repository at this point in the history
  • Loading branch information
tadeuzagallo committed Apr 2, 2015
1 parent dac0887 commit c6079d5
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 327 deletions.
2 changes: 2 additions & 0 deletions Libraries/RCTTest/RCTTestModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// This is used to give meaningful names to snapshot image files.
@property (nonatomic, assign) SEL testSelector;

@property (nonatomic, weak) UIView *view;

- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;

@end
30 changes: 11 additions & 19 deletions Libraries/RCTTest/RCTTestRunner.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@

#define TIMEOUT_SECONDS 240

@interface RCTRootView (Testing)

- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider;

@end

@implementation RCTTestRunner
{
FBSnapshotTestController *_snapshotController;
Expand Down Expand Up @@ -75,17 +66,17 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictiona

RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
testModule.testSelector = test;

RCTRootView *rootView = [[RCTRootView alloc] _initWithBundleURL:[NSURL URLWithString:_script]
moduleName:moduleName
launchOptions:nil
moduleProvider:^{
return @[testModule];
}];
[testModule setValue:rootView forKey:@"_view"];
rootView.frame = CGRectMake(0, 0, 320, 2000);
RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:_script
moduleProvider:^(){
return @[testModule];
}
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName];
testModule.view = rootView;
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
rootView.initialProperties = initialProps;
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices

NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
Expand All @@ -94,8 +85,9 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictiona
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
error = [[RCTRedBox sharedInstance] currentErrorMessage];
}
[rootView invalidate];
[rootView removeFromSuperview];
[rootView.bridge invalidate];
[rootView invalidate];
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
vc.view = nil;
[[RCTRedBox sharedInstance] dismiss];
Expand Down
1 change: 1 addition & 0 deletions Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)object

- (void)invalidate
{
[_jsQueue cancelAllOperations];
_socket.delegate = nil;
[_socket closeWithCode:1000 reason:@"Invalidated"];
_socket = nil;
Expand Down
10 changes: 10 additions & 0 deletions React/Base/RCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <UIKit/UIKit.h>

#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
Expand All @@ -24,11 +26,15 @@
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);

extern NSString *const RCTReloadBridge;

/**
* Async batched bridge used to communicate with the JavaScript application.
*/
@interface RCTBridge : NSObject <RCTInvalidating>

@property (nonatomic, assign, readonly, getter=isLoaded) BOOL loaded;

/**
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
Expand All @@ -55,6 +61,8 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
*/
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;

@property (nonatomic, strong) Class executorClass;

/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input.
Expand Down Expand Up @@ -89,4 +97,6 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
*/
+ (BOOL)hasValidJSExecutor;

- (void)reload;

@end
116 changes: 99 additions & 17 deletions React/Base/RCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>

#import "RCTContextExecutor.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
#import "RCTWebViewExecutor.h"

/**
* Must be kept in sync with `MessageQueue.js`.
Expand All @@ -34,6 +39,8 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};

NSString *const RCTReloadBridge = @"RCTReloadBridge";

/**
* This function returns the module name for a given class.
*/
Expand Down Expand Up @@ -125,6 +132,8 @@ @implementation RCTModuleMethod
NSString *_methodName;
}

static Class _globalExecutorClass;

- (instancetype)initWithMethodName:(NSString *)methodName
JSMethodName:(NSString *)JSMethodName
{
Expand Down Expand Up @@ -497,33 +506,41 @@ @implementation RCTBridge
RCTSparseArray *_modulesByID;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSString *_bundlePath;
NSDictionary *_launchOptions;
RCTBridgeModuleProviderBlock _moduleProvider;
BOOL _loaded;
}

static id<RCTJavaScriptExecutor> _latestJSExecutor;

- (instancetype)initWithBundlePath:(NSString *)bundlepath
- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
if ((self = [super init])) {
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
_bundlePath = bundlePath;
_moduleProvider = block;
_launchOptions = launchOptions;
[self setUp];
[self bindKeys];
}

return self;
}

- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor
- (void)setUp
{
_javaScriptExecutor = executor;
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
if ([NSStringFromClass(executorClass) isEqualToString:@"RCTWebViewExecutor"]) {
_javaScriptExecutor = [[RCTWebViewExecutor alloc] initWithWebView:[[UIWebView alloc] init]];
} else {
_javaScriptExecutor = [[executorClass alloc] init];
}
_latestJSExecutor = _javaScriptExecutor;
[self setUp];
}
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);

- (void)setUp
{
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
Expand Down Expand Up @@ -574,11 +591,58 @@ - (void)setUp
dispatch_semaphore_signal(semaphore);
}];

if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogError(@"JavaScriptExecutor took too long to inject JSON object");

if (_bundlePath != nil) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:[NSURL URLWithString:_bundlePath]
onComplete:^(NSError *error) {
_loaded = YES;
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
}
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
;
}
}];
}
}

- (void)bindKeys
{
#if TARGET_IPHONE_SIMULATOR
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[self reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_executorClass = Nil;
[self reload];
}];

[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!_executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[self reload];
}];
#endif
}

- (NSDictionary *)modules
{
Expand All @@ -602,18 +666,20 @@ - (BOOL)isValid

- (void)invalidate
{
[[NSNotificationCenter defaultCenter] removeObserver:self];

// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});

// Release executor
if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil;
}
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;

// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});

// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
Expand All @@ -624,6 +690,7 @@ - (void)invalidate
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_modulesByName = nil;
_loaded = NO;
}

/**
Expand All @@ -647,9 +714,11 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);

if (self.loaded) {
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]];
}
}

- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
Expand Down Expand Up @@ -793,6 +862,19 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
return YES;
}

- (void)reload
{
if (_loaded) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadViewsNotification
object:self];
}
}

+ (BOOL)hasValidJSExecutor
{
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
Expand Down
4 changes: 2 additions & 2 deletions React/Base/RCTDevMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

#import <UIKit/UIKit.h>

@class RCTRootView;
@class RCTBridge;

@interface RCTDevMenu : NSObject

- (instancetype)initWithRootView:(RCTRootView *)rootView;
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)show;

@end
Loading

0 comments on commit c6079d5

Please sign in to comment.