2015-03-26 00:33:54 +00:00
|
|
|
/**
|
|
|
|
* 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 "RCTDevMenu.h"
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
#import "RCTBridge.h"
|
2015-04-30 16:50:22 +00:00
|
|
|
#import "RCTDefines.h"
|
|
|
|
#import "RCTKeyCommands.h"
|
2015-04-19 19:55:46 +00:00
|
|
|
#import "RCTLog.h"
|
2015-04-20 11:55:05 +00:00
|
|
|
#import "RCTProfile.h"
|
2015-03-26 00:33:54 +00:00
|
|
|
#import "RCTRootView.h"
|
2015-04-02 14:33:21 +00:00
|
|
|
#import "RCTSourceCode.h"
|
2015-04-19 19:55:46 +00:00
|
|
|
#import "RCTUtils.h"
|
2015-03-26 00:33:54 +00:00
|
|
|
|
2015-04-30 16:50:22 +00:00
|
|
|
#if RCT_DEV
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
@interface RCTBridge (Profiling)
|
2015-04-17 11:02:37 +00:00
|
|
|
|
|
|
|
- (void)startProfiling;
|
|
|
|
- (void)stopProfiling;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
|
|
|
|
|
|
|
|
@implementation UIWindow (RCTDevMenu)
|
|
|
|
|
|
|
|
- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
|
|
{
|
|
|
|
if (event.subtype == UIEventSubtypeMotionShake) {
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-04-30 16:50:22 +00:00
|
|
|
@interface RCTDevMenu () <RCTBridgeModule, RCTInvalidating, UIActionSheetDelegate>
|
2015-03-26 00:33:54 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTDevMenu
|
2015-04-11 22:08:00 +00:00
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
NSTimer *_updateTimer;
|
|
|
|
UIActionSheet *_actionSheet;
|
2015-04-11 22:08:00 +00:00
|
|
|
}
|
2015-03-26 00:33:54 +00:00
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
|
|
|
+ (void)initialize
|
|
|
|
{
|
|
|
|
// We're swizzling here because it's poor form to override methods in a category,
|
|
|
|
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
|
|
|
|
// no need to call the original implementation.
|
|
|
|
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)init
|
2015-03-26 00:33:54 +00:00
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
if ((self = [super init])) {
|
|
|
|
|
|
|
|
_shakeToShow = YES;
|
|
|
|
_liveReloadPeriod = 1.0; // 1 second
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(showOnShake)
|
|
|
|
name:RCTShowDevMenuNotification
|
|
|
|
object:nil];
|
2015-04-30 16:50:22 +00:00
|
|
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
|
|
|
|
|
|
__weak RCTDevMenu *weakSelf = self;
|
|
|
|
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
|
|
|
|
|
|
|
|
// Workaround around the first cmd+D not working: http://openradar.appspot.com/19613391
|
|
|
|
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+R
|
|
|
|
// will work like a charm!
|
|
|
|
[commands registerKeyCommandWithInput:@""
|
|
|
|
modifierFlags:UIKeyModifierCommand
|
|
|
|
action:NULL];
|
|
|
|
|
|
|
|
// reload in debug mode
|
|
|
|
[commands registerKeyCommandWithInput:@"d"
|
|
|
|
modifierFlags:UIKeyModifierCommand
|
|
|
|
action:^(UIKeyCommand *command) {
|
|
|
|
__strong RCTDevMenu *strongSelf = weakSelf;
|
|
|
|
[strongSelf show];
|
|
|
|
}];
|
|
|
|
#endif
|
|
|
|
|
2015-03-26 00:33:54 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)showOnShake
|
|
|
|
{
|
|
|
|
if (_shakeToShow) {
|
|
|
|
[self show];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-26 00:33:54 +00:00
|
|
|
- (void)show
|
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
if (_actionSheet) {
|
|
|
|
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 *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
|
2015-04-20 11:55:05 +00:00
|
|
|
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
|
2015-04-19 19:55:46 +00:00
|
|
|
|
|
|
|
UIActionSheet *actionSheet =
|
|
|
|
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
|
|
|
delegate:self
|
|
|
|
cancelButtonTitle:@"Cancel"
|
|
|
|
destructiveButtonTitle:nil
|
|
|
|
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
|
|
|
|
2015-03-26 00:33:54 +00:00
|
|
|
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
2015-04-19 19:55:46 +00:00
|
|
|
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
|
2015-04-30 16:50:22 +00:00
|
|
|
_actionSheet = actionSheet;
|
2015-03-26 00:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
_actionSheet = nil;
|
|
|
|
|
|
|
|
switch (buttonIndex) {
|
|
|
|
case 0: {
|
|
|
|
[_bridge reload];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
|
|
|
|
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
|
|
|
|
[_bridge reload];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2: {
|
|
|
|
Class cls = NSClassFromString(@"RCTWebViewExecutor");
|
|
|
|
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
|
|
|
|
[_bridge reload];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: {
|
|
|
|
self.liveReloadEnabled = !_liveReloadEnabled;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4: {
|
|
|
|
self.profilingEnabled = !_profilingEnabled;
|
|
|
|
break;
|
2015-04-17 11:02:37 +00:00
|
|
|
}
|
2015-04-19 19:55:46 +00:00
|
|
|
default:
|
|
|
|
break;
|
2015-03-26 00:33:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
- (void)setProfilingEnabled:(BOOL)enabled
|
2015-03-26 00:33:54 +00:00
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
if (_profilingEnabled == enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_profilingEnabled = enabled;
|
2015-04-20 11:55:05 +00:00
|
|
|
if (RCTProfileIsProfiling()) {
|
2015-04-19 19:55:46 +00:00
|
|
|
[_bridge stopProfiling];
|
|
|
|
} else {
|
|
|
|
[_bridge startProfiling];
|
2015-03-26 00:33:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
- (void)setLiveReloadEnabled:(BOOL)enabled
|
2015-03-26 00:33:54 +00:00
|
|
|
{
|
2015-04-19 19:55:46 +00:00
|
|
|
if (_liveReloadEnabled == enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_liveReloadEnabled = enabled;
|
|
|
|
if (_liveReloadEnabled) {
|
|
|
|
|
|
|
|
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
|
|
|
|
target:self
|
|
|
|
selector:@selector(pollForUpdates)
|
|
|
|
userInfo:nil
|
|
|
|
repeats:YES];
|
|
|
|
} else {
|
|
|
|
|
|
|
|
[_updateTimer invalidate];
|
|
|
|
_updateTimer = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
|
|
|
|
{
|
|
|
|
_liveReloadPeriod = liveReloadPeriod;
|
|
|
|
if (_liveReloadEnabled) {
|
|
|
|
self.liveReloadEnabled = NO;
|
|
|
|
self.liveReloadEnabled = YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)pollForUpdates
|
|
|
|
{
|
|
|
|
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
|
|
|
if (!sourceCodeModule) {
|
|
|
|
RCTLogError(@"RCTSourceCode module not found");
|
|
|
|
self.liveReloadEnabled = NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
|
|
|
|
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
|
|
|
|
queue:[[NSOperationQueue alloc] init]
|
|
|
|
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
|
|
|
|
|
|
|
|
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
|
|
|
|
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
|
|
|
|
[_bridge reload];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isValid
|
|
|
|
{
|
|
|
|
return !_liveReloadEnabled || _updateTimer != nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)invalidate
|
|
|
|
{
|
|
|
|
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
|
|
|
|
[_updateTimer invalidate];
|
|
|
|
_updateTimer = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-04-30 16:50:22 +00:00
|
|
|
#else // Unvailable
|
|
|
|
|
|
|
|
@implementation RCTDevMenu
|
|
|
|
|
|
|
|
- (void)show {}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
@implementation RCTBridge (RCTDevMenu)
|
|
|
|
|
|
|
|
- (RCTDevMenu *)devMenu
|
|
|
|
{
|
|
|
|
return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
|
2015-03-26 00:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|